Интеллектуальное вариационное расширение на основе строки формата - PullRequest
4 голосов
/ 04 апреля 2009

У меня есть демон, который читает файл конфигурации, чтобы знать, где что-то написать. В файле конфигурации есть такая строка:

output = /tmp/foo/%d/%s/output

Или это может выглядеть так:

output = /tmp/foo/%s/output/%d

... или просто так:

output = /tmp/foo/%s/output

... или, наконец:

output = /tmp/output

У меня есть эта строка как cfg-> pathfmt в моей программе. То, что я пытаюсь сделать сейчас, - это придумать какой-нибудь умный способ его использования.

Немного больше объяснений, путь может содержать до двух компонентов, которые будут отформатированы. % d будет расширен как идентификатор задания (int),% s как имя задания (строка). Пользователь может захотеть использовать один, оба или ни одного в файле конфигурации. Мне нужно знать, что они хотят и в каком порядке, прежде чем я наконец передам это snprintf (). Я могу отчасти сузить это, но я все еще хочу поговорить со strtok (), и это кажется уродливым.

Я хочу предоставить пользователям такую ​​гибкость, однако я теряюсь в поисках разумного, портативного способа его реализации. Я также в полной и полной потере, как начать поиск этого.

Я был бы очень рад, если бы:

  • Кто-то может помочь мне сузить поисковую фразу, чтобы найти хорошие примеры
  • Кто-то может опубликовать ссылку на какой-либо проект OSS, реализующий этот
  • Кто-то может опубликовать код псевдо

Мне не нужен код, написанный для меня, я просто застрял на том, что (я думаю) должно быть чем-то очень простым и нуждается в некоторой помощи, чтобы сделать первый укус. Я действительно чувствую, что я слишком много думаю и упускаю из виду очевидное.

Конечным результатом должна быть логическая функция, подобная этой:

bool output_sugar(const char *fmt, int jobid, const char *jobname, struct job *j);

Затем он будет вызывать snprintf () (разумно) для j-> outpath, возвращая false, если какой-то мусор (т.е.%, за которым следует что-то не s, d или%) находится в строке конфигурации (или его ноль). Проверка работоспособности проста, у меня просто немного времени на получение числа (и порядка) аргументов для правильного форматирования.

Спасибо заранее. Кроме того, не стесняйтесь редактировать этот заголовок, если у вас есть для этого репутация, как я уже сказал, я не совсем уверен, как задать вопрос в одной строке. Я думаю, что мне нужен парсер , но он чувствует себя неловко, используя полноценный лексер / парсер для обработки одной простой строки.

Ответы [ 3 ]

8 голосов
/ 05 апреля 2009

Да, вам нужен какой-то парсер. Это не должно быть сложным, хотя:

void format_filename(const char *fmt, int jobid, const char *jobname,
                     char *buffer, size_t buflen)
{
    char *end = buffer + buflen - 1;
    const char *src = fmt;
    char *dst = buffer;
    char c;
    assert(buffer != 0 && fmt != 0 && buflen != 0 && jobname != 0);
    while ((c = *src++) != '\0')
    {
        if (dst >= end)
            err_exit("buffer overflow in %s(): format = %s\n",
                     __func__, fmt);
        else if (c != '%')
            *dst++ = c;
        else if ((c = *src++) == '\0' || c == '%')
        {
            *dst++ = '%';
            if (c == '\0')
                break;
        }
        else if (c == 's')
        {
            size_t len = strlen(jobname);
            if (len > end - dst)
                err_exit("buffer overflow on jobname in %s(): format = %s\n",
                         __func__, fmt);
            else
            {
                strcpy(dst, jobname);
                dst += len;
            }
        }
        else if (c == 'd')
        {
             int nchars = snprintf(dst, end - dst, "%d", jobid);
             if (nchars < 0 || nchars >= end - dst)
                 err_exit("format error on jobid in %s(); format = %s\n",
                          __func__, fmt);
             dst += nchars;
        }
        else
            err_exit("invalid format character %d in %s(): format = %s\n",
                     c, __func__, fmt);
    }
    *dst = '\0';
}

Теперь проверенный код. Обратите внимание, что он поддерживает нотацию «%%», чтобы позволить пользователю вставлять один «%» в вывод. Кроме того, он обрабатывает один «%» в конце строки как допустимый и эквивалентный «%%». Вызывает err_exit () при ошибке; Вы можете выбрать альтернативные стратегии ошибок в соответствии с вашей системой. Я просто предполагаю, что вы включили <assert.h>, <stdio.h> и <string.h> и заголовок для функции err_exit() (variadic).


Тестовый код ...

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>

static void err_exit(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    exit(1);
}

... затем format_filename() как указано выше, затем ...

#define DIM(x) (sizeof(x)/sizeof(*(x)))

static const char *format[] =
{
    "/tmp/%d/name/%s",
    "/tmp/%s/number/%d",
    "/tmp/%s.%d%%",
    "/tmp/%",
};

int main(void)
{
    char buffer[64];
    size_t i;

    for (i = 0; i < DIM(format); i++)
    {
        format_filename(format[i], 1234, "job-name", buffer, sizeof(buffer));
        printf("fmt = %-20s; name = %s\n", format[i], buffer);
    }

    return(0);
}
5 голосов
/ 04 апреля 2009

Использование strtok подвержено ошибкам. Вы можете рассматривать ваши переменные как мини-язык, используя (fl) lex и yacc. Здесь есть простое руководство

%{
#include <stdio.h>
%}

%%
%d                      printf("%04d",jobid);
%s                      printf("%s",stripspaces(dirname));
%%

Я создал оболочку ODBC, которая позволяла бы вам делать такие вещи, как dbprintf («вставить в бла-значения% s% D% T% Y», все здесь ...); Но это было много лет назад, и я укусил его и проанализировал строку формата, используя strtok.

1 голос
/ 05 апреля 2009

Если количество опций невелико и в противном случае вам не требуется / не требуется дополнительная гибкость и сложность синтаксического анализатора, вы можете просто выполнить поиск каждой потенциальной замещающей подстроки с помощью strstr ().

Если у вас есть только два варианта, вы можете сносно создать четырехразветвленную структуру if / else (только A, только B, оба с A до B, оба с B до A), в которой можно вызывать sprintf () с правильно упорядоченные аргументы. В противном случае выполните несколько вызовов sprintf (), каждый из которых заменяет только первый маркер замены в строке формата. (Это подразумевает создание списка необходимых замен и сортировку их в порядке появления ...)

...