10.4 Среда разработки приложений — Filter

Предыдущая  Содержание  Следующая V*D*V

Обработка командной строки является обычной задачей, характерной для всех программ-фильтров. Надо собрать связанные или раздельные флаги и значения параметров, необходимо распознать два знака "минус" -- в качестве метки конца списка опций и один знак "минус" - дополнительно как поток стандартного ввода, и, возможно, надо прочитать поток стандартного ввода или каждый файл, заданный аргументами. С этой целью все программы-фильтры содержат более или менее одинаковый код и макрос, похожий на MAIN [Sch87, глава 15], или функции, такие как getopt(3), помогающие поддерживать стандарты, но почему, если уж на то пошло, отвергается такой код?

Класс Filter разработан как единообразная реализация для обработки командной строки для всех программ-фильтров. Он может быть назван средой разработки приложений, потому что устанавливает основные правила и базовую структуру для большой группы приложений. Метод mainLoop() содержит раз и навсегда сделанную обработку командной строки и использует функции обратного вызова, чтобы позволить клиенту иметь дело с извлечёнными аргументами:

 

% mainLoop {                    // (self, argv)

%casts

    self -> progname = * argv ++;

 

    while (* argv && ** argv == ’-’) {

        switch (* ++ * argv) {

        case 0:                 // один -

            -- * argv;          // ... это имя файла

            break;              // ... и конец параметров

        case ’-’:

            if (! (* argv)[1]) {// два -

                ++ argv;        // ... игнорируем

                break;          // ... и конец параметров

            }

        default:                // остальной набор флагов

            do

                if (self -> flag) {

                    self -> argv = argv;

                    self -> flag(self -> delegate,

                                        self, ** argv);

                    argv = self -> argv;

                }

                else {

                    fprintf(stderr,

                        "%s: -%c: no flags allowed\n",

                        self -> progname, ** argv);

                    return 1;

                }

            while (* ++ * argv);

            ++ argv;

            continue;

        }

        break;

    }

 

Внешний цикл обрабатывает параметры, пока не достигает нулевого указателя, завершающего массив argv[], или пока параметр не начинается со знака "минус". Один или два знака "минус" завершают внешний цикл операторами break.

Внутренний цикл передаёт каждый символ одного параметра в функцию обработки флага flag, предоставленную делегатом. Если делегат решает, что флаг представляет параметр со значением, метод argval() обеспечивает обратный вызов от делегата в фильтр для получения значения параметра:

 

% argval {                          // (self)

    const char * result;

%casts

    assert(self -> argv && * self -> argv);

 

    if ((* self -> argv)[1])        // -fvalue

        result = ++ * self -> argv;

    else if (self -> argv[1])       // -f value

        result = * ++ self -> argv;

    else                            // аргументов больше нет

        result = NULL;

    while ((* self -> argv)[1])     // пропускаем текст

        ++ * self -> argv;

    return result;

}

 

Значение параметра представляет собой либо оставшуюся часть аргумента флага, либо следующий аргумент если таковой имеется. self -> argv продвигается дальше, чтобы внутренний цикл mainLoop() мог завершиться.

После того, как из командной строки параметры были выбраны, остаются аргументы с именами файлов. Если их нет, программа-фильтр работает с потоком стандартного ввода. mainLoop() продолжается следующим образом:

 

    if (* argv)

        do

            result = doit(self, * argv);

        while (! result && * ++ argv);

    else

        result = doit(self, NULL);

 

    if (self -> quit)

        result = self -> quit(self -> delegate, self);

    return result;

}

 

Позаботиться о единственном аргументе имени файла предоставляем doit(). Нулевой указатель представляет ситуацию, когда аргументов нет. doit() возвращает код завершения: другие аргументы обрабатываются только если он равен нулю.

 

% doit {                    // (self, arg)

    FILE * fp;

    int result = 0;

%casts

    if (self -> name)

        return self -> name(self -> delegate, self, arg);

 

    if (! arg || strcmp(arg, "-") == 0)

        fp = stdin, clearerr(fp);

    else if (! * arg) {

        fprintf(stderr, "%s: null filename\n",

                                self -> progname);

        return 1;

    }

    else if (! (fp = fopen(arg, "r"))) {

        perror(arg);

        return 1;

    }

 

Клиент может предоставить функцию для обработки аргумента с именем файла. Иначе, в случае нулевого указателя или знака "минус" в виде аргумента, doit() соединяется с stdin; другие имена файлов открываются для чтения. Как только файл открыт, клиент может передать работу ещё одной функции обратного вызова, или doit() выделит динамический буфер и начнёт читать строки:

 

    if (self -> file)

        result = self -> file(self -> delegate, self, arg, fp);

    else {

        if (! self -> buf) {

            self -> blen = BUFSIZ;

            self -> buf = malloc(self -> blen);

            assert(self -> buf);

        }

 

        while (fgets(self -> buf, self -> blen, fp))

            if (self -> line && (result =

                self -> line(self -> delegate, self, arg,

                                                self -> buf)))

                break;

 

        if (self -> wrap)

            result = self -> wrap(self -> delegate, self, arg);

    }

 

    if (fp != stdin)

        fclose(fp);

 

    if (fflush(stdout), ferror(stdout)) {

        fprintf(stderr, "%s: output error\n", self -> progname);

        result = 1;

    }

    return result;

}

 

С помощью ещё двух функций обратного вызова клиент может получать каждую текстовую строку и выполнять действия очистки, как только файл завершен. wc использует эти функции. doit() освобождает указатель файла и удостоверяется, что вывод был успешно записан.

Если клиентский класс реализует ориентированные на строку обратные вызовы от класса Filter, он должен знать о том, что имеет дело с текстовыми строками. fgets() читает входные данные до переполнения своего буфера или пока не будет найден символ новой строки. Дополнительный код в doit() увеличивает динамический буфер до требуемого размера, но передаёт клиенту только буфер, но не его длину. fgets() не возвращает количество считанных символов, то есть если во входных данных есть нулевой байт, у клиента нет способа его пройти, потому что нулевой байт может фактически означать конец последнего буфера файла без символа завершения строки.

 

Предыдущая  Содержание  Следующая