source: tags/svn.1.5.4/AWTI/AWTI_import.cxx

Last change on this file was 8359, checked in by westram, 12 years ago
  • added 'Never ask again' switch to most modal question dialogs. This is a workaround to make it possible to work with macros where modal questions are used. See also #179
    • added unique IDs to all calls to aw_question / AW_repeated_question::get_answer
    • replaced most calls to aw_popup_ok with aw_message (most of them only worked-around the non-standard way of EDIT4 to show aw_message)
  • added 'Reactivate questions' to all properties menus
  • hardcoded unused default-params from aw_ask_sure, aw_popup_ok + aw_popup_exit
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 46.7 KB
Line 
1// ============================================================= //
2//                                                               //
3//   File      : AWTI_import.cxx                                 //
4//   Purpose   :                                                 //
5//                                                               //
6//   Institute of Microbiology (Technical University Munich)     //
7//   http://www.arb-home.de/                                     //
8//                                                               //
9// ============================================================= //
10
11#include "awti_imp_local.hxx"
12
13#include <seqio.hxx>
14#include <awt.hxx>
15#include <item_sel_list.h>
16#include <aw_advice.hxx>
17#include <aw_file.hxx>
18#include <aw_awar.hxx>
19#include <AW_rename.hxx>
20#include <aw_msg.hxx>
21#include <aw_root.hxx>
22#include <aw_question.hxx>
23#include <GenomeImport.h>
24#include <GEN.hxx>
25#include <adGene.h>
26
27#include <arb_progress.h>
28#include <arb_strbuf.h>
29#include <arb_file.h>
30
31#include <climits>
32
33using namespace std;
34
35static awtcig_struct awtcig;
36#define MAX_COMMENT_LINES 2000
37
38inline const char *name_only(const char *fullpath) {
39    const char *lslash = strrchr(fullpath, '/');
40    return lslash ? lslash+1 : fullpath;
41}
42
43
44static GB_ERROR not_in_match_error(const char *cmd) {
45    return GBS_global_string("Command '%s' may only appear after 'MATCH'", cmd);
46}
47
48static GB_ERROR read_import_format(const char *fullfile, input_format_struct *ifo, bool *var_set, bool included) {
49    GB_ERROR  error = 0;
50    FILE     *in    = fopen(fullfile, "rt");
51
52    input_format_per_line *pl = ifo->pl;
53
54    if (!in) {
55        const char *name = name_only(fullfile);
56
57        if (included) {
58            error = GB_export_IO_error("including", name);
59        }
60        else {
61            error = strchr(name, '*')
62                ? "Please use 'AUTO DETECT' or manually select an import format"
63                : GB_export_IO_error("loading import filter", name);
64        }
65    }
66    else {
67        char   *s1, *s2;
68        size_t  lineNumber    = 0;
69        bool    include_error = false;
70
71        while (!error && SEQIO_read_string_pair(in, s1, s2, lineNumber)) {
72
73#define GLOBAL_COMMAND(cmd) (!error && strcmp(s1, cmd) == 0)
74#define MATCH_COMMAND(cmd)  (!error && strcmp(s1, cmd) == 0 && (pl || !(error = not_in_match_error(cmd))))
75
76            if (GLOBAL_COMMAND("MATCH")) {
77                pl = new input_format_per_line;
78
79                pl->defined_at = GBS_global_string_copy("%zi,%s", lineNumber, name_only(fullfile));
80                pl->next       = ifo->pl;           // this concatenates the filters to the front -> the list is reversed below
81                ifo->pl        = pl;
82                pl->match      = GBS_remove_escape(s2);
83                pl->type       = GB_STRING;
84
85                if (ifo->autotag) pl->mtag = strdup(ifo->autotag); // will be overwritten by TAG command
86            }
87            else if (MATCH_COMMAND("SRT"))         { reassign(pl->srt, s2); }
88            else if (MATCH_COMMAND("ACI"))         { reassign(pl->aci, s2); }
89            else if (MATCH_COMMAND("WRITE"))       { reassign(pl->write, s2); }
90            else if (MATCH_COMMAND("WRITE_INT"))   { reassign(pl->write, s2); pl->type = GB_INT; }
91            else if (MATCH_COMMAND("WRITE_FLOAT")) { reassign(pl->write, s2); pl->type = GB_FLOAT; }
92            else if (MATCH_COMMAND("APPEND"))      { reassign(pl->append, s2); }
93            else if (MATCH_COMMAND("SETVAR")) {
94                if (strlen(s2) != 1 || s2[0]<'a' || s2[0]>'z') {
95                    error = "Allowed variable names are a-z";
96                }
97                else {
98                    var_set[s2[0]-'a'] = true;
99                    reassign(pl->setvar, s2);
100                }
101            }
102            else if (MATCH_COMMAND("TAG")) {
103                if (s2[0]) reassign(pl->mtag, s2);
104                else error = "Empty TAG is not allowed";
105            }
106            else if (GLOBAL_COMMAND("AUTODETECT"))               { reassign(ifo->autodetect,    s2); }
107            else if (GLOBAL_COMMAND("SYSTEM"))                   { reassign(ifo->system,        s2); }
108            else if (GLOBAL_COMMAND("NEW_FORMAT"))               { reassign(ifo->new_format,    s2); ifo->new_format_lineno = lineNumber; }
109            else if (GLOBAL_COMMAND("BEGIN"))                    { reassign(ifo->begin,         s2); }
110            else if (GLOBAL_COMMAND("FILETAG"))                  { reassign(ifo->filetag,       s2); }
111            else if (GLOBAL_COMMAND("SEQUENCESRT"))              { reassign(ifo->sequencesrt,   s2); }
112            else if (GLOBAL_COMMAND("SEQUENCEACI"))              { reassign(ifo->sequenceaci,   s2); }
113            else if (GLOBAL_COMMAND("SEQUENCEEND"))              { reassign(ifo->sequenceend,   s2); }
114            else if (GLOBAL_COMMAND("END"))                      { reassign(ifo->end,           s2); }
115            else if (GLOBAL_COMMAND("AUTOTAG"))                  { reassign(ifo->autotag,       s2); }
116            else if (GLOBAL_COMMAND("SEQUENCESTART"))            { reassign(ifo->sequencestart, s2); ifo->read_this_sequence_line_too = 1; }
117            else if (GLOBAL_COMMAND("SEQUENCEAFTER"))            { reassign(ifo->sequencestart, s2); ifo->read_this_sequence_line_too = 0; }
118            else if (GLOBAL_COMMAND("KEYWIDTH"))                 { ifo->tab            = atoi(s2); }
119            else if (GLOBAL_COMMAND("SEQUENCECOLUMN"))           { ifo->sequencecolumn = atoi(s2); }
120            else if (GLOBAL_COMMAND("CREATE_ACC_FROM_SEQUENCE")) { ifo->autocreateacc  = 1; }
121            else if (GLOBAL_COMMAND("DONT_GEN_NAMES"))           { ifo->noautonames    = 1; }
122            else if (GLOBAL_COMMAND("INCLUDE")) {
123                char *dir         = AW_extract_directory(fullfile);
124                char *includeFile = GBS_global_string_copy("%s/%s", dir, s2);
125
126                error = read_import_format(includeFile, ifo, var_set, true);
127                if (error) include_error = true;
128
129                free(includeFile);
130                free(dir);
131            }
132            else {
133                bool ifnotset  = GLOBAL_COMMAND("IFNOTSET");
134                bool setglobal = GLOBAL_COMMAND("SETGLOBAL");
135
136                if (ifnotset || setglobal) {
137                    if (s2[0]<'a' || s2[0]>'z') {
138                        error = "Allowed variable names are a-z";
139                    }
140                    else {
141                        int off = 1;
142                        while (isblank(s2[off])) off++;
143                        if (!s2[off]) error = GBS_global_string("Expected two arguments in '%s'", s2);
144                        else {
145                            char        varname = s2[0];
146                            const char *arg2    = s2+off;
147
148                            if (ifnotset) {
149                                if (ifo->variable_errors.get(varname)) {
150                                    error = GBS_global_string("Redefinition of IFNOTSET %c", varname);
151                                }
152                                else {
153                                    ifo->variable_errors.set(varname, arg2);
154                                }
155                            }
156                            else {
157                                awti_assert(setglobal);
158                                ifo->global_variables.set(varname, arg2);
159                                var_set[varname-'a'] = true;
160                            }
161                        }
162                    }
163                }
164                else if (!error) {
165                    error = GBS_global_string("Unknown command '%s'", s1);
166                }
167            }
168
169            free(s1);
170            free(s2);
171        }
172
173        if (error) {
174            error = GBS_global_string("%sin line %zi of %s '%s':\n%s",
175                                      include_error ? "included " : "",
176                                      lineNumber,
177                                      included ? "file" : "import format",
178                                      name_only(fullfile),
179                                      error);
180        }
181
182        fclose(in);
183
184#undef MATCH_COMMAND
185#undef GLOBAL_COMMAND
186    }
187
188    return error;
189}
190
191static GB_ERROR awtc_read_import_format(const char *file) {
192    char *fullfile = strdup(GB_path_in_ARBHOME(file));
193
194    delete awtcig.ifo;
195    awtcig.ifo = new input_format_struct;
196
197    bool var_set[IFS_VARIABLES];
198    for (int i = 0; i<IFS_VARIABLES; i++) var_set[i] = false;
199
200    GB_ERROR error = read_import_format(fullfile, awtcig.ifo, var_set, false);
201
202
203    for (int i = 0; i<IFS_VARIABLES && !error; i++) {
204        bool ifnotset = awtcig.ifo->variable_errors.get(i+'a');
205        if (var_set[i]) {
206            bool isglobal = awtcig.ifo->global_variables.get(i+'a');
207            if (!ifnotset && !isglobal) { // don't warn if variable is global
208                error = GBS_global_string("Warning: missing IFNOTSET for variable '%c'", 'a'+i);
209            }
210        }
211        else {
212            if (ifnotset) {
213                error = GBS_global_string("Warning: useless IFNOTSET for unused variable '%c'", 'a'+i);
214            }
215        }
216    }
217
218    // reverse order of match list (was appended backwards during creation)
219    if (awtcig.ifo->pl) awtcig.ifo->pl = awtcig.ifo->pl->reverse(0);
220
221    free(fullfile);
222
223    return error;
224}
225
226input_format_per_line::input_format_per_line() {
227    memset((char *)this, 0, sizeof(*this));
228}
229
230input_format_per_line::~input_format_per_line() {
231    free(match);
232    free(aci);
233    free(srt);
234    free(mtag);
235    free(append);
236    free(write);
237    free(setvar);
238    free(defined_at);
239
240    delete next;
241}
242
243input_format_struct::input_format_struct() {
244    memset((char *)this, 0, sizeof(*this));
245}
246
247input_format_struct::~input_format_struct() {
248    free(autodetect);
249    free(system);
250    free(new_format);
251    free(begin);
252    free(sequencestart);
253    free(sequenceend);
254    free(sequencesrt);
255    free(sequenceaci);
256    free(filetag);
257    free(autotag);
258    free(end);
259    free(b1);
260    free(b2);
261
262    delete pl;
263}
264
265static void awtc_check_input_format(AW_window *aww) {
266    AW_root  *root = aww->get_root();
267    StrArray  files;
268    GBS_read_dir(files, GB_path_in_ARBLIB("import"), "*.ift");
269
270    char     buffer[AWTC_IMPORT_CHECK_BUFFER_SIZE+10];
271    GB_ERROR error = 0;
272
273    int matched       = -1;
274    int first         = -1;
275    int matched_count = 0;
276
277    AW_awar *awar_filter   = root->awar(AWAR_FORM"/file_name");
278    char    *prev_selected = awar_filter->read_string();
279
280    for (int idx = 0; files[idx] && !error; ++idx) {
281        const char *filtername = files[idx];
282        awar_filter->write_string(filtername);
283
284        GB_ERROR form_err = awtc_read_import_format(filtername);
285        if (form_err) {
286            aw_message(form_err);
287        }
288        else {
289            if (awtcig.ifo->autodetect) {      // detectable
290                char *autodetect = GBS_remove_escape(awtcig.ifo->autodetect);
291                delete awtcig.ifo; awtcig.ifo = 0;
292
293                FILE *in = 0;
294                {
295                    char *f = root->awar(AWAR_FILE)->read_string();
296
297                    if (f[0]) {
298                        const char *com = GBS_global_string("cat %s 2>/dev/null", f);
299                        in              = popen(com, "r");
300                    }
301                    free(f);
302                    if (!in) error = "Cannot open any input file";
303                }
304                if (in) {
305                    int size = fread(buffer, 1, AWTC_IMPORT_CHECK_BUFFER_SIZE, in);
306                    pclose(in);
307                    if (size>=0) {
308                        buffer[size] = 0;
309                        if (GBS_string_matches(buffer, autodetect, GB_MIND_CASE)) {
310                            // format found!
311                            matched_count++;
312                            if (matched == -1) matched = idx; // remember first/next found format
313                            if (first == -1) first     = idx; // remember first found format
314
315                            if (strcmp(filtername, prev_selected) == 0) { // previously selected filter
316                                matched = -1; // select next (or first.. see below)
317                            }
318                        }
319                    }
320                }
321                free(autodetect);
322            }
323        }
324    }
325
326    const char *select = 0;
327    switch (matched_count) {
328        case 0:
329            AW_advice("Not all formats can be auto-detected.\n"
330                      "Some need to be selected manually.",
331                       AW_ADVICE_TOGGLE,
332                      "No format auto-detected",
333                      "arb_import.hlp");
334
335            select = "unknown.ift";
336            break;
337
338        default:
339            AW_advice("Several import filters matched during auto-detection.\n"
340                      "Click 'AUTO DETECT' again to select next matching import-filter.",
341                      AW_ADVICE_TOGGLE,
342                      "Several formats detected",
343                      "arb_import.hlp");
344
345            // fall-through
346        case 1:
347            if (matched == -1) {
348                // wrap around to top (while searching next matching filter)
349                // or re-select the one matching filter (if it was previously selected)
350                awti_assert(first != -1);
351                matched = first;
352            }
353            awti_assert(matched != -1);
354            select = files[matched];  // select 1st matching filter
355            break;
356    }
357
358    if (error) {
359        select = "unknown.ift";
360        aw_message(error);
361    }
362
363    free(prev_selected);
364    awar_filter->write_string(select);
365}
366
367static int awtc_next_file() {
368    if (awtcig.in) fclose(awtcig.in);
369    if (awtcig.current_file_idx<0) awtcig.current_file_idx = 0;
370
371    int result = 1;
372    while (result == 1 && awtcig.filenames[awtcig.current_file_idx]) {
373        const char *origin_file_name = awtcig.filenames[awtcig.current_file_idx++];
374
375        const char *sorigin   = strrchr(origin_file_name, '/');
376        if (!sorigin) sorigin = origin_file_name;
377        else sorigin++;
378
379        GB_ERROR  error          = 0;
380        char     *mid_file_name  = 0;
381        char     *dest_file_name = 0;
382
383        if (awtcig.ifo2 && awtcig.ifo2->system) {
384            {
385                const char *sorigin_nameonly            = strrchr(sorigin, '/');
386                if (!sorigin_nameonly) sorigin_nameonly = sorigin;
387
388                char *mid_name = GB_unique_filename(sorigin_nameonly, "tmp");
389                mid_file_name  = GB_create_tempfile(mid_name);
390                free(mid_name);
391
392                if (!mid_file_name) error = GB_await_error();
393            }
394
395            if (!error) {
396                char *srt = GBS_global_string_copy("$<=%s:$>=%s", origin_file_name, mid_file_name);
397                char *sys = GBS_string_eval(awtcig.ifo2->system, srt, 0);
398
399                arb_progress::show_comment(GBS_global_string("exec '%s'", awtcig.ifo2->system));
400
401                error                        = GBK_system(sys);
402                if (!error) origin_file_name = mid_file_name;
403
404                free(sys);
405                free(srt);
406            }
407        }
408
409        if (!error && awtcig.ifo->system) {
410            {
411                const char *sorigin_nameonly            = strrchr(sorigin, '/');
412                if (!sorigin_nameonly) sorigin_nameonly = sorigin;
413
414                char *dest_name = GB_unique_filename(sorigin_nameonly, "tmp");
415                dest_file_name  = GB_create_tempfile(dest_name);
416                free(dest_name);
417
418                if (!dest_file_name) error = GB_await_error();
419            }
420
421            awti_assert(dest_file_name || error);
422
423            if (!error) {
424                char *srt = GBS_global_string_copy("$<=%s:$>=%s", origin_file_name, dest_file_name);
425                char *sys = GBS_string_eval(awtcig.ifo->system, srt, 0);
426
427                arb_progress::show_comment(GBS_global_string("Converting File %s", awtcig.ifo->system));
428
429                error                        = GBK_system(sys);
430                if (!error) origin_file_name = dest_file_name;
431
432                free(sys);
433                free(srt);
434            }
435        }
436
437        if (!error) {
438            awtcig.in = fopen(origin_file_name, "r");
439
440            if (awtcig.in) {
441                result = 0;
442            }
443            else {
444                error = GBS_global_string("Error: Cannot open file %s\n", origin_file_name);
445            }
446        }
447
448        if (mid_file_name) {
449            awti_assert(GB_is_privatefile(mid_file_name, false));
450            GB_unlink_or_warn(mid_file_name, &error);
451            free(mid_file_name);
452        }
453        if (dest_file_name) {
454            GB_unlink_or_warn(dest_file_name, &error);
455            free(dest_file_name);
456        }
457
458        if (error) aw_message(error);
459    }
460
461    return result;
462}
463static char *awtc_read_line(int tab, char *sequencestart, char *sequenceend) {
464    /* two modes:   tab == 0 -> read single lines,
465       different files are separated by sequenceend,
466       tab != 0 join lines that start after position tab,
467       joined lines are separated by '|'
468       except lines that match sequencestart
469       (they may be part of sequence if read_this_sequence_line_too = 1 */
470
471    static char *in_queue = 0;      // read data
472    static int b2offset = 0;
473    const int BUFSIZE = 8000;
474    const char *SEPARATOR = "|";    // line separator
475    struct input_format_struct *ifo;
476    ifo = awtcig.ifo;
477    char *p;
478
479    if (!ifo->b1) ifo->b1 = (char*)calloc(BUFSIZE, 1);
480    if (!ifo->b2) ifo->b2 = (char*)calloc(BUFSIZE, 1);
481    if (!awtcig.in) {
482        if (awtc_next_file()) {
483            if (in_queue) {
484                char *s = in_queue;
485                in_queue = 0;
486                return s;
487            }
488            return 0;
489        }
490    }
491
492
493    if (!tab) {
494        if (in_queue) {
495            char *s = in_queue;
496            in_queue = 0;
497            return s;
498        }
499        p = SEQIO_fgets(ifo->b1, BUFSIZE-3, awtcig.in);
500        if (!p) {
501            sprintf(ifo->b1, "%s", sequenceend);
502            if (awtcig.in) fclose(awtcig.in); awtcig.in = 0;
503            p = ifo->b1;
504        }
505        int len = strlen(p)-1;
506        while (len>=0) {
507            if (p[len] == '\n' || p[len] == 13) p[len--] = 0;
508            else break;
509        }
510        return ifo->b1;
511    }
512
513    b2offset = 0;
514    ifo->b2[0] = 0;
515
516    if (in_queue) {
517        b2offset = 0;
518        strncpy(ifo->b2+b2offset, in_queue, BUFSIZE - 4- b2offset);
519        b2offset += strlen(ifo->b2+b2offset);
520        in_queue = 0;
521        if (GBS_string_matches(ifo->b2, sequencestart, GB_MIND_CASE)) return ifo->b2;
522    }
523    while (1) {
524        p = SEQIO_fgets(ifo->b1, BUFSIZE-3, awtcig.in);
525        if (!p) {
526            if (awtcig.in) fclose(awtcig.in); awtcig.in = 0;
527            break;
528        }
529        int len = strlen(p)-1;
530        while (len>=0) {
531            if (p[len] == '\n' || p[len] == 13) p[len--] = 0;
532            else break;
533        }
534
535
536        if (GBS_string_matches(ifo->b1, sequencestart, GB_MIND_CASE)) {
537            in_queue = ifo->b1;
538            return ifo->b2;
539        }
540
541        int i;
542        for (i=0; i<=tab; i++) if (ifo->b1[i] != ' ') break;
543
544        if (i < tab) {
545            in_queue = ifo->b1;
546            return ifo->b2;
547        }
548        strncpy(ifo->b2+b2offset, SEPARATOR, BUFSIZE - 4- b2offset);
549        b2offset += strlen(ifo->b2+b2offset);
550
551        p = ifo->b1;
552        if (b2offset>0) while (*p==' ') p++;    // delete spaces in second line
553
554        strncpy(ifo->b2+b2offset, p, BUFSIZE - 4- b2offset);
555        b2offset += strlen(ifo->b2+b2offset);
556    }
557    in_queue = 0;
558    return ifo->b2;
559}
560
561static void awtc_write_entry(GBDATA *gbd, const char *key, const char *str, const char *tag, int append, GB_TYPES type = GB_STRING) {
562    if (!gbd) return;
563
564    {
565        while (str[0] == ' ' || str[0] == '\t' || str[0] == '|') str++;
566        int i = strlen(str)-1;
567        while (i >= 0 && (str[i] == ' ' || str[i] == '\t' || str[i] == '|' || str[i] == 13)) {
568            i--;
569        }
570
571        if (i<0) return;
572
573        i++;
574        if (str[i]) { // need to cut trailing whitespace?
575            char *copy = (char*)malloc(i+1);
576            memcpy(copy, str, i);
577            copy[i]    = 0;
578
579            awtc_write_entry(gbd, key, copy, tag, append, type);
580
581            free(copy);
582            return;
583        }
584    }
585
586    GBDATA *gbk = GB_entry(gbd, key);
587
588    if (type != GB_STRING) {
589        if (!gbk) gbk=GB_create(gbd, key, type);
590        switch (type) {
591            case GB_INT:
592                GB_write_int(gbk, atoi(str));
593                break;
594            case GB_FLOAT:
595                GB_write_float(gbk, atof(str));
596                break;
597            default:
598                awti_assert(0);
599                break;
600        }
601        return;
602    }
603
604    if (!gbk || !append) {
605        if (!gbk) gbk=GB_create(gbd, key, GB_STRING);
606
607        if (tag) {
608            GBS_strstruct *s = GBS_stropen(10000);
609            GBS_chrcat(s, '[');
610            GBS_strcat(s, tag);
611            GBS_strcat(s, "] ");
612            GBS_strcat(s, str);
613            char *val = GBS_strclose(s);
614            GB_write_string(gbk, val);
615            free(val);
616        }
617        else {
618            if (strcmp(key, "name") == 0) {
619                char *nstr = GBT_create_unique_species_name(awtcig.gb_main, str);
620                GB_write_string(gbk, nstr);
621                free(nstr);
622            }
623            else {
624                GB_write_string(gbk, str);
625            }
626        }
627        return;
628    }
629
630    const char *strin = GB_read_char_pntr(gbk);
631
632    int   len    = strlen(str) + strlen(strin);
633    int   taglen = tag ? (strlen(tag)+2) : 0;
634    char *buf    = (char *)GB_calloc(sizeof(char), len+2+taglen+1);
635
636    if (tag) {
637        char *regexp = (char*)GB_calloc(sizeof(char), taglen+3);
638        sprintf(regexp, "*[%s]*", tag);
639
640        if (!GBS_string_matches(strin, regexp, GB_IGNORE_CASE)) { // if tag does not exist yet
641            sprintf(buf, "%s [%s] %s", strin, tag, str); // prefix with tag
642        }
643        free(regexp);
644    }
645
646    if (buf[0] == 0) {
647        sprintf(buf, "%s %s", strin, str);
648    }
649
650    GB_write_string(gbk, buf);
651    free(buf);
652    return;
653}
654
655static string expandSetVariables(const SetVariables& variables, const string& source, bool& error_occurred, const input_format_struct *ifo) {
656    string                 dest;
657    string::const_iterator norm_start = source.begin();
658    string::const_iterator p          = norm_start;
659    error_occurred                    = false;
660
661    while (p != source.end()) {
662        if (*p == '$') {
663            ++p;
664            if (*p == '$') { // '$$' -> '$'
665                dest.append(1, *p);
666            }
667            else { // real variable
668                const string *value = variables.get(*p);
669                if (!value) {
670                    const string *user_error = ifo->variable_errors.get(*p);
671
672                    char *error = 0;
673                    if (user_error) {
674                        error = GBS_global_string_copy("%s (variable '$%c' not set yet)", user_error->c_str(), *p);
675                    }
676                    else {
677                        error = GBS_global_string_copy("Variable '$%c' not set (missing SETVAR or SETGLOBAL?)", *p);
678                    }
679
680                    dest.append(GBS_global_string("<%s>", error));
681                    GB_export_error(error);
682                    free(error);
683                    error_occurred = true;
684                }
685                else {
686                    dest.append(*value);
687                }
688            }
689            ++p;
690        }
691        else {
692            dest.append(1, *p);
693            ++p;
694        }
695    }
696    return dest;
697}
698
699static GB_ERROR awtc_read_data(char *ali_name, int security_write)
700{
701    char        num[6];
702    char        text[100];
703    static int  counter         = 0;
704    GBDATA     *gb_species_data = GB_search(GB_MAIN, "species_data", GB_CREATE_CONTAINER);
705    GBDATA     *gb_species;
706    char       *p;
707
708    input_format_struct   *ifo = awtcig.ifo;
709    input_format_per_line *pl  = 0;
710
711    while (1) {             // go to the start
712        p = awtc_read_line(0, ifo->sequencestart, ifo->sequenceend);
713        if (!p || !ifo->begin || GBS_string_matches(p, ifo->begin, GB_MIND_CASE)) break;
714    }
715    if (!p) return "Cannot find start of file: Wrong format or empty file";
716
717    // lets start !!!!!
718    while (p) {
719        SetVariables variables(ifo->global_variables);
720
721        counter++;
722        if (counter % 10 == 0) {
723            sprintf(text, "Reading species %i", counter);
724            arb_progress::show_comment(text);
725        }
726
727        gb_species = GB_create_container(gb_species_data, "species");
728        sprintf(text, "spec%i", counter);
729        GBT_readOrCreate_char_pntr(gb_species, "name", text); // set default if missing
730        if (awtcig.filenames[1]) {      // multiple files !!!
731            const char *f = strrchr(awtcig.filenames[awtcig.current_file_idx-1], '/');
732            if (!f) f = awtcig.filenames[awtcig.current_file_idx-1];
733            else f++;
734            awtc_write_entry(gb_species, "file", f, ifo->filetag, 0);
735        }
736        int line;
737        static bool never_warn = false;
738        int max_line = never_warn ? INT_MAX : MAX_COMMENT_LINES;
739
740        for (line=0; line<=max_line; line++) {
741            sprintf(num, "%i  ", line);
742            if (line == max_line) {
743                const char *file = NULL;
744                if (awtcig.filenames[awtcig.current_file_idx]) file = awtcig.filenames[awtcig.current_file_idx];
745                GB_ERROR msg = GB_export_errorf("A database entry in file '%s' is longer than %i lines.\n"
746                                                "    This might be the result of a wrong input format\n"
747                                                "    or a long comment in a sequence\n", file, line);
748
749                switch (aw_question("import_long_lines", msg, "Continue Reading,Continue Reading (Never ask again),Abort")) {
750                    case 0:
751                        max_line *= 2;
752                        break;
753                    case 1:
754                        max_line = INT_MAX;
755                        never_warn = true;
756                        break;
757                    case 2:
758                        break;
759                }
760            }
761            GB_ERROR    error      = 0;
762            if (strlen(p) > ifo->tab) {
763                for (pl = ifo->pl; !error && pl; pl=pl->next) {
764                    const char *what_error = 0;
765                    if (GBS_string_matches(p, pl->match, GB_MIND_CASE)) {
766                        char *dup = p+ifo->tab;
767                        while (*dup == ' ' || *dup == '\t') dup++;
768
769                        char *s              = 0;
770                        char *dele           = 0;
771
772                        if (pl->srt) {
773                            bool   err_flag;
774                            string expanded = expandSetVariables(variables, pl->srt, err_flag, ifo);
775                            if (err_flag) error = GB_await_error();
776                            else {
777                                dele           = s = GBS_string_eval(dup, expanded.c_str(), gb_species);
778                                if (!s) error  = GB_await_error();
779                            }
780                            if (error) what_error = "SRT";
781                        }
782                        else {
783                            s = dup;
784                        }
785
786                        if (!error && pl->aci) {
787                            bool   err_flag;
788                            string expanded     = expandSetVariables(variables, pl->aci, err_flag, ifo);
789                            if (err_flag) error = GB_await_error();
790                            else {
791                                dup           = dele;
792                                dele          = s = GB_command_interpreter(GB_MAIN, s, expanded.c_str(), gb_species, 0);
793                                if (!s) error = GB_await_error();
794                                free(dup);
795                            }
796                            if (error) what_error = "ACI";
797                        }
798
799                        if (!error && (pl->append || pl->write)) {
800                            char *field = 0;
801                            char *tag   = 0;
802
803                            {
804                                bool   err_flag;
805                                string expanded_field = expandSetVariables(variables, string(pl->append ? pl->append : pl->write), err_flag, ifo);
806                                if (err_flag) error   = GB_await_error();
807                                else   field          = GBS_string_2_key(expanded_field.c_str());
808                                if (error) what_error = "APPEND or WRITE";
809                            }
810
811                            if (!error && pl->mtag) {
812                                bool   err_flag;
813                                string expanded_tag = expandSetVariables(variables, string(pl->mtag), err_flag, ifo);
814                                if (err_flag) error = GB_await_error();
815                                else   tag          = GBS_string_2_key(expanded_tag.c_str());
816                                if (error) what_error = "TAG";
817                            }
818
819                            if (!error) {
820                                awtc_write_entry(gb_species, field, s, tag, pl->append != 0, pl->type);
821                            }
822                            free(tag);
823                            free(field);
824                        }
825
826                        if (!error && pl->setvar) variables.set(pl->setvar[0], s);
827                        free(dele);
828                    }
829
830                    if (error) {
831                        error = GBS_global_string("'%s'\nin %s of MATCH (defined at #%s)", error, what_error, pl->defined_at);
832                    }
833                }
834            }
835
836            if (error) {
837                return GBS_global_string("%s\nwhile parsing line #%i of species #%i", error, line, counter);
838            }
839
840            if (GBS_string_matches(p, ifo->sequencestart, GB_MIND_CASE)) goto read_sequence;
841
842            p = awtc_read_line(ifo->tab, ifo->sequencestart, ifo->sequenceend);
843            if (!p) break;
844        }
845        return GB_export_errorf("No Start of Sequence found (%i lines read)", max_line);
846
847    read_sequence :
848        {
849            char          *sequence;
850            GBS_strstruct *strstruct = GBS_stropen(5000);
851            int            linecnt;
852
853            for (linecnt = 0; ; linecnt++) {
854                if (linecnt || !ifo->read_this_sequence_line_too) {
855                    p = awtc_read_line(0, ifo->sequencestart, ifo->sequenceend);
856                }
857                if (!p) break;
858                if (ifo->sequenceend && GBS_string_matches(p, ifo->sequenceend, GB_MIND_CASE)) break;
859                if (strlen(p) <= ifo->sequencecolumn) continue;
860                GBS_strcat(strstruct, p+ifo->sequencecolumn);
861            }
862            sequence = GBS_strclose(strstruct);
863
864            GBDATA *gb_data = GBT_create_sequence_data(gb_species, ali_name, "data", GB_STRING, security_write);
865            if (ifo->sequencesrt) {
866                char *h = GBS_string_eval(sequence, ifo->sequencesrt, gb_species);
867                if (!h) return GB_await_error();
868                freeset(sequence, h);
869            }
870
871            if (ifo->sequenceaci) {
872                char *h = GB_command_interpreter(GB_MAIN, sequence, ifo->sequenceaci, gb_species, 0);
873                free(sequence);
874                if (!h) return GB_await_error();
875                sequence = h;
876            }
877
878            GB_write_string(gb_data, sequence);
879
880            GBDATA *gb_acc = GB_search(gb_species, "acc", GB_FIND);
881            if (!gb_acc && ifo->autocreateacc) {
882                char buf[100];
883                long id = GBS_checksum(sequence, 1, ".-");
884                sprintf(buf, "ARB_%lX", id);
885                gb_acc = GB_search(gb_species, "acc", GB_STRING);
886                GB_write_string(gb_acc, buf);
887            }
888            free(sequence);
889        }
890        while (1) {             // go to the start of an species
891            if (!p || !ifo->begin || GBS_string_matches(p, ifo->begin, GB_MIND_CASE)) break;
892            p = awtc_read_line(0, ifo->sequencestart, ifo->sequenceend);
893        }
894    }
895    return 0;
896}
897
898static void AWTC_import_go_cb(AW_window *aww) // Import sequences into new or existing database
899{
900    AW_root *awr                     = aww->get_root();
901    bool     is_genom_db             = false;
902    bool     delete_db_type_if_error = false; // delete db type (genome/normal) in case of error ?
903    {
904        bool           read_genom_db = (awr->awar(AWAR_READ_GENOM_DB)->read_int() != IMP_PLAIN_SEQUENCE);
905        GB_transaction ta(GB_MAIN);
906
907        delete_db_type_if_error = (GB_entry(GB_MAIN, GENOM_DB_TYPE) == 0);
908        is_genom_db             = GEN_is_genome_db(GB_MAIN, read_genom_db);
909
910        if (read_genom_db!=is_genom_db) {
911            if (is_genom_db) {
912                aw_message("You can only import whole genom sequences into a genom database.");
913            }
914            else {
915                aw_message("You can't import whole genom sequences into a non-genom ARB database.");
916            }
917            return;
918        }
919    }
920
921    GB_ERROR error = 0;
922
923    GB_change_my_security(GB_MAIN, 6);
924
925    GB_begin_transaction(GB_MAIN); // first transaction start
926    char *ali_name;
927    {
928        char *ali = awr->awar(AWAR_ALI)->read_string();
929        ali_name = GBS_string_eval(ali, "*=ali_*1:ali_ali_=ali_", 0);
930        free(ali);
931    }
932
933    error = GBT_check_alignment_name(ali_name);
934
935    int ali_protection = awr->awar(AWAR_ALI_PROTECTION)->read_int();
936    if (!error) {
937        char *ali_type;
938        ali_type = awr->awar(AWAR_ALI_TYPE)->read_string();
939
940        if (is_genom_db && strcmp(ali_type, "dna")!=0) {
941            error = "You must set the alignment type to dna when importing genom sequences.";
942        }
943        else {
944            GBT_create_alignment(GB_MAIN, ali_name, 2000, 0, ali_protection, ali_type);
945        }
946        free(ali_type);
947    }
948
949    bool ask_generate_names = true;
950
951    if (!error) {
952        if (is_genom_db) {
953            // import genome flatfile into ARB-genome database:
954
955            char     *mask = awr->awar(AWAR_FILE)->read_string();
956            StrArray  fnames;
957            GBS_read_dir(fnames, mask, NULL);
958
959            if (fnames.empty()) {
960                error = GB_export_error("Cannot find selected file");
961            }
962            else {
963                int successfull_imports = 0;
964                int failed_imports      = 0;
965                int count;
966
967                for (count = 0; fnames[count]; ++count) ; // count filenames
968
969                GBDATA *gb_species_data = GB_search(GB_MAIN, "species_data", GB_CREATE_CONTAINER);
970                ImportSession import_session(gb_species_data, count*10);
971
972                // close the above transaction and do each importfile in separate transaction
973                // to avoid that all imports are undone by transaction abort happening in case of error
974                GB_commit_transaction(GB_MAIN);
975
976                arb_progress progress("Reading input files", count);
977
978                for (int curr = 0; !error && fnames[curr]; ++curr) {
979                    GB_ERROR error_this_file =  0;
980
981                    GB_begin_transaction(GB_MAIN);
982                    {
983                        const char *lslash = strrchr(fnames[curr], '/');
984                        progress.subtitle(GBS_global_string("%i/%i: %s", curr+1, count, lslash ? lslash+1 : fnames[curr]));
985                    }
986
987#if defined(DEBUG)
988                    fprintf(stderr, "import of '%s'\n", fnames[curr]);
989#endif // DEBUG
990                    error_this_file = GI_importGenomeFile(import_session, fnames[curr], ali_name);
991
992                    if (!error_this_file) {
993                        GB_commit_transaction(GB_MAIN);
994                        successfull_imports++;
995                        delete_db_type_if_error = false;
996                    }
997                    else { // error occurred during import
998                        error_this_file = GBS_global_string("'%s' not imported\nReason: %s", fnames[curr], error_this_file);
999                        GB_warningf("Import error: %s", error_this_file);
1000                        GB_abort_transaction(GB_MAIN);
1001                        failed_imports++;
1002                    }
1003
1004                    progress.inc_and_check_user_abort(error);
1005                }
1006
1007                if (!successfull_imports) {
1008                    error = "Nothing has been imported";
1009                }
1010                else {
1011                    GB_warningf("%i of %i files were imported with success", successfull_imports, (successfull_imports+failed_imports));
1012                }
1013
1014                // now open another transaction to "undo" the transaction close above
1015                GB_begin_transaction(GB_MAIN);
1016            }
1017
1018            free(mask);
1019        }
1020        else {
1021            // import to non-genome ARB-db :
1022
1023            {
1024                // load import filter:
1025                char *file = awr->awar(AWAR_FORM"/file_name")->read_string();
1026
1027                if (!strlen(file)) {
1028                    error = "Please select a form";
1029                }
1030                else {
1031                    error = awtc_read_import_format(file);
1032                    if (!error && awtcig.ifo->new_format) {
1033                        awtcig.ifo2 = awtcig.ifo;
1034                        awtcig.ifo  = 0;
1035
1036                        error = awtc_read_import_format(awtcig.ifo2->new_format);
1037                        if (!error) {
1038                            if (awtcig.ifo->new_format) {
1039                                error = GBS_global_string("in line %zi of import filter '%s':\n"
1040                                                          "Only one level of form nesting (NEW_FORMAT) allowed",
1041                                                          awtcig.ifo->new_format_lineno, name_only(awtcig.ifo2->new_format));
1042                            }
1043                        }
1044                        if (error) {
1045                            error = GBS_global_string("in format used in line %zi of '%s':\n%s",
1046                                                      awtcig.ifo2->new_format_lineno, name_only(file), error);
1047                        }
1048                    }
1049                }
1050                free(file);
1051            }
1052
1053            {
1054                char *f = awr->awar(AWAR_FILE)->read_string();
1055                GBS_read_dir(awtcig.filenames, f, NULL);
1056                free(f);
1057            }
1058
1059            if (awtcig.filenames[0] == 0) {
1060                error = GB_export_error("Cannot find selected file(s)");
1061            }
1062
1063            if (!error) {
1064                arb_progress progress("Reading input files");
1065
1066                error = awtc_read_data(ali_name, ali_protection);
1067                if (error) {
1068                    error = GBS_global_string("Error: %s\nwhile reading file %s", error, awtcig.filenames[awtcig.current_file_idx-1]);
1069                }
1070                else {
1071                    if (awtcig.ifo->noautonames || (awtcig.ifo2 && awtcig.ifo2->noautonames)) {
1072                        ask_generate_names = false;
1073                    }
1074                    else {
1075                        ask_generate_names = true;
1076                    }
1077                }
1078            }
1079
1080            delete awtcig.ifo; awtcig.ifo = 0;
1081            delete awtcig.ifo2; awtcig.ifo2 = 0;
1082
1083            if (awtcig.in) {
1084                fclose(awtcig.in);
1085                awtcig.in = 0;
1086            }
1087
1088            awtcig.filenames.erase();
1089            awtcig.current_file_idx = 0;
1090        }
1091    }
1092    free(ali_name);
1093
1094    bool call_func = true; // shall awtcig.func be called ?
1095    if (error) {
1096        GB_abort_transaction(GB_MAIN);
1097
1098        if (delete_db_type_if_error) {
1099            // delete db type, if it was initialized above
1100            // (avoids 'can't import'-error, if file-type (genome-file/species-file) is changed after a failed try)
1101            GB_transaction  ta(GB_MAIN);
1102            GBDATA         *db_type = GB_entry(GB_MAIN, GENOM_DB_TYPE);
1103            if (db_type) GB_delete(db_type);
1104        }
1105
1106        call_func = false;
1107    }
1108    else {
1109        aww->hide(); // import window stays open in case of error
1110
1111        arb_progress progress("Checking and Scanning database", 2+ask_generate_names); // 2 or 3 passes
1112        progress.subtitle("Pass 1: Check entries");
1113
1114        // scan for hidden/unknown fields :
1115        species_field_selection_list_rescan(GB_MAIN, FIELD_FILTER_NDS, RESCAN_REFRESH);
1116        if (is_genom_db) gene_field_selection_list_rescan(GB_MAIN, FIELD_FILTER_NDS, RESCAN_REFRESH);
1117
1118        GBT_mark_all(GB_MAIN, 1);
1119        progress.inc();
1120        progress.subtitle("Pass 2: Check sequence lengths");
1121        GBT_check_data(GB_MAIN, 0);
1122
1123        GB_commit_transaction(GB_MAIN);
1124        progress.inc();
1125
1126        if (ask_generate_names) {
1127            if (aw_question("generate_short_names",
1128                            "You may generate short names using the full_name and accession entry of the species",
1129                            "Generate new short names (recommended),Use found names")==0)
1130            {
1131                progress.subtitle("Pass 3: Generate unique names");
1132                error = AW_select_nameserver(GB_MAIN, awtcig.gb_other_main);
1133                if (!error) {
1134                    error = AWTC_pars_names(GB_MAIN);
1135                }
1136            }
1137            progress.inc();
1138        }
1139    }
1140
1141    if (error) aw_message(error);
1142
1143    GB_change_my_security(GB_MAIN, 0);
1144
1145    if (call_func) awtcig.func(awr, awtcig.cd1, awtcig.cd2);
1146}
1147
1148class AliNameAndType {
1149    string name_, type_;
1150public:
1151    AliNameAndType(const char *ali_name, const char *ali_type) : name_(ali_name), type_(ali_type) {}
1152    AliNameAndType(const AliNameAndType& other) : name_(other.name_), type_(other.type_) {}
1153
1154    const char *name() const { return name_.c_str(); }
1155    const char *type() const { return type_.c_str(); }
1156};
1157
1158static AliNameAndType last_ali("ali_new", "rna"); // last selected ali for plain import (aka non-flatfile import)
1159
1160
1161void AWTC_import_set_ali_and_type(AW_root *awr, const char *ali_name, const char *ali_type, GBDATA *gbmain) {
1162    bool           switching_to_GENOM_ALIGNMENT = strcmp(ali_name, GENOM_ALIGNMENT) == 0;
1163    static GBDATA *last_valid_gbmain            = 0;
1164
1165    if (gbmain) last_valid_gbmain = gbmain;
1166
1167    AW_awar *awar_name = awr->awar(AWAR_ALI);
1168    AW_awar *awar_type = awr->awar(AWAR_ALI_TYPE);
1169
1170    if (switching_to_GENOM_ALIGNMENT) {
1171        // read and store current settings
1172        char *curr_ali_name = awar_name->read_string();
1173        char *curr_ali_type = awar_type->read_string();
1174
1175        last_ali = AliNameAndType(curr_ali_name, curr_ali_type);
1176
1177        free(curr_ali_name);
1178        free(curr_ali_type);
1179    }
1180
1181    awar_name->write_string(ali_name);
1182    awar_type->write_string(ali_type);
1183
1184    if (last_valid_gbmain) { // detect default write protection for alignment
1185        GB_transaction ta(last_valid_gbmain);
1186        GBDATA *gb_ali            = GBT_get_alignment(last_valid_gbmain, ali_name);
1187        int     protection_to_use = 4;         // default protection
1188
1189        if (gb_ali) {
1190            GBDATA *gb_write_security = GB_entry(gb_ali, "alignment_write_security");
1191            if (gb_write_security) {
1192                protection_to_use = GB_read_int(gb_write_security);
1193            }
1194        }
1195        else {
1196            GB_clear_error();
1197        }
1198        awr->awar(AWAR_ALI_PROTECTION)->write_int(protection_to_use);
1199    }
1200}
1201
1202static void genom_flag_changed(AW_root *awr) {
1203    if (awr->awar(AWAR_READ_GENOM_DB)->read_int() == IMP_PLAIN_SEQUENCE) {
1204        AWTC_import_set_ali_and_type(awr, last_ali.name(), last_ali.type(), 0);
1205        awr->awar_string(AWAR_FORM"/filter", ".ift");
1206    }
1207    else {
1208        AWTC_import_set_ali_and_type(awr, GENOM_ALIGNMENT, "dna", 0);
1209        awr->awar_string(AWAR_FORM"/filter", ".fit"); // *hack* to hide normal import filters
1210    }
1211}
1212
1213static void import_window_close_cb(AW_window *aww) {
1214    if (awtcig.doExit) {
1215        GB_close(awtcig.gb_main);
1216        exit(EXIT_SUCCESS);
1217    }
1218    AW_POPDOWN(aww);
1219}
1220
1221GBDATA *open_AWTC_import_window(AW_root *awr, const char *defname, bool do_exit, GBDATA *gb_main, AWTC_RCB(func), AW_CL cd1, AW_CL cd2)
1222{
1223    static AW_window_simple *aws = 0;
1224
1225#if defined(WARN_TODO)
1226#warning where is awtcig.gb_main closed
1227    // it is either (currently not) closed by merge tool
1228    // or closed as main db in ARB_NTREE
1229#endif
1230    awtcig.gb_main = GB_open("noname.arb", "wc");
1231    awtcig.func    = func;
1232    awtcig.cd1     = cd1;
1233    awtcig.cd2     = cd2;
1234
1235#if defined(DEBUG)
1236    AWT_announce_db_to_browser(awtcig.gb_main, "New database (import)");
1237#endif // DEBUG
1238
1239    awtcig.gb_other_main = gb_main;
1240
1241    awtcig.doExit = do_exit; // change/set behavior of CLOSE button
1242
1243    AW_create_fileselection_awars(awr, AWAR_FILE_BASE, ".", "", defname);
1244    AW_create_fileselection_awars(awr, AWAR_FORM, GB_path_in_ARBLIB("import"), ".ift", "*");
1245
1246    awr->awar_string(AWAR_ALI, "dummy"); // these defaults are never used
1247    awr->awar_string(AWAR_ALI_TYPE, "dummy"); // they are overwritten by AWTC_import_set_ali_and_type
1248    awr->awar_int(AWAR_ALI_PROTECTION, 0); // which is called via genom_flag_changed() below
1249
1250    awr->awar(AWAR_READ_GENOM_DB)->add_callback(genom_flag_changed);
1251    genom_flag_changed(awr);
1252
1253    if (!aws) {
1254        aws = new AW_window_simple;
1255
1256        aws->init(awr, "ARB_IMPORT", "ARB IMPORT");
1257        aws->load_xfig("awt/import_db.fig");
1258
1259        aws->at("close");
1260        aws->callback(import_window_close_cb);
1261        aws->create_button("CLOSE", "CLOSE", "C");
1262
1263        aws->at("help");
1264        aws->callback(AW_POPUP_HELP, (AW_CL)"arb_import.hlp");
1265        aws->create_button("HELP", "HELP", "H");
1266
1267        AW_create_fileselection(aws, AWAR_FILE_BASE, "imp_", "PWD", true, true); // select import filename
1268        AW_create_fileselection(aws, AWAR_FORM, "", "ARBHOME", false, false); // select import filter
1269
1270        aws->at("auto");
1271        aws->callback(awtc_check_input_format);
1272        aws->create_autosize_button("AUTO_DETECT", "AUTO DETECT", "A");
1273
1274        aws->at("ali");
1275        aws->create_input_field(AWAR_ALI, 4);
1276
1277        aws->at("type");
1278        aws->create_option_menu(AWAR_ALI_TYPE);
1279        aws->insert_option("dna", "d", "dna");
1280        aws->insert_option("rna", "r", "rna");
1281        aws->insert_option("protein", "p", "ami");
1282        aws->update_option_menu();
1283
1284        aws->at("protect");
1285        aws->create_option_menu(AWAR_ALI_PROTECTION);
1286        aws->insert_option("0", "0", 0);
1287        aws->insert_option("1", "1", 1);
1288        aws->insert_option("2", "2", 2);
1289        aws->insert_option("3", "3", 3);
1290        aws->insert_default_option("4", "4", 4);
1291        aws->insert_option("5", "5", 5);
1292        aws->insert_option("6", "6", 6);
1293        aws->update_option_menu();
1294
1295        aws->at("genom");
1296        aws->create_toggle_field(AWAR_READ_GENOM_DB);
1297        aws->sens_mask(AWM_EXP);
1298        aws->insert_toggle("Import genome data in EMBL, GenBank and DDBJ format", "e", IMP_GENOME_FLATFILE);
1299        aws->sens_mask(AWM_ALL);
1300        aws->insert_toggle("Import selected format", "f", IMP_PLAIN_SEQUENCE);
1301        aws->update_toggle_field();
1302
1303        aws->at("go");
1304        aws->callback(AWTC_import_go_cb);
1305        aws->highlight();
1306        aws->create_button("GO", "GO", "G");
1307    }
1308    aws->activate();
1309    return GB_MAIN;
1310}
Note: See TracBrowser for help on using the repository browser.