Разобрать длинный метод тестирования командной строки - PullRequest
3 голосов
/ 20 мая 2009

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

Поскольку я добавил каждую функцию, я просто "зарегистрировал" способ обработки этого аргумента, добавив несколько строк в этот метод.

Однако этот метод теперь неприглядный, подвержен ошибкам и труден для понимания. Вот пример более короткого из двух методов, в настоящее время обрабатывающих это:

//All double dash arguments modify global options of the program,
//such as --all --debug --timeout etc.
void consoleParser::wordArgParse(std::vector<criterion *> *results)
{
    TCHAR const *compareCurWordArg = curToken.c_str()+2;
    if (!_tcsicmp(compareCurWordArg,_T("all")))
    {
        globalOptions::showall = TRUE;
    } else if (!_tcsnicmp(compareCurWordArg,_T("custom"),6))
    {
        if (curToken[9] == L':')
        {
            globalOptions::display = curToken.substr(10,curToken.length()-11);
        } else
        {
            globalOptions::display = curToken.substr(9,curToken.length()-10);
        }
    } else if (*compareCurWordArg == L'c' || *compareCurWordArg == L'C')
    {
        if (curToken[3] == L':')
        {
            globalOptions::display = curToken.substr(5,curToken.length()-6);
        } else
        {
            globalOptions::display = curToken.substr(4,curToken.length()-5);
        }
    } else if (!_tcsicmp(compareCurWordArg,_T("debug")))
    {
        globalOptions::debug = TRUE;
    } else if (!_tcsicmp(compareCurWordArg,L"expand"))
    {
        globalOptions::expandRegex = false;
    } else if (!_tcsicmp(compareCurWordArg,L"fileLook"))
    {
        globalOptions::display = L"---- #f ----#nCompany: #d#nFile Description: #e#nFile Version: #g"
        L"#nProduct Name: #i#nCopyright: #j#nOriginal file name: #k#nFile Size: #u#nCreated Time: #c"
        L"#nModified Time: #m#nAccessed Time: #a#nMD5: #5#nSHA1: #1";
    } else if (!_tcsicmp(compareCurWordArg,_T("peinfo")))
    {
        globalOptions::display = _T("[#p] #f");
    } else if (!_tcsicmp(compareCurWordArg,L"enable-filesystem-redirector-64"))
    {
        globalOptions::disable64Redirector = false;
    } else if (!_tcsnicmp(compareCurWordArg,_T("encoding"),8))
    {
        //Performance enhancement -- encoding compare only done once.
        compareCurWordArg += 8;
        if (!_tcsicmp(compareCurWordArg,_T("acp")))
        {
            globalOptions::encoding = globalOptions::ENCODING_TYPE_ACP;
        } else if (!_tcsicmp(compareCurWordArg,_T("oem")))
        {
            globalOptions::encoding = globalOptions::ENCODING_TYPE_OEM;
        } else if (!_tcsicmp(compareCurWordArg,_T("utf8")))
        {
            globalOptions::encoding = globalOptions::ENCODING_TYPE_UTF8;
        } else if (!_tcsicmp(compareCurWordArg,_T("utf16")))
        {
            globalOptions::encoding = globalOptions::ENCODING_TYPE_UTF16;
        } else
        {
            throw eMsg(L"Unrecognised encoding word argument!\r\nValid choices are --encodingACP --encodingOEM --encodingUTF8 and --encodingUTF16. Terminate.");
        }
    } else if (!_tcsnicmp(compareCurWordArg,L"files",5))
    {
        compareCurWordArg += 5;
        if (*compareCurWordArg == L':') compareCurWordArg++;
        std::wstring filePath(compareCurWordArg);
        globalOptions::regexes.insert(globalOptions::regexes.end(), new filesRegexPlaceHolder);
        results->insert(results->end(),new filesRegexPlaceHolder);
        boost::algorithm::trim_if(filePath,std::bind2nd(std::equal_to<wchar_t>(),L'"'));
        loadFiles(filePath);
    } else if (!_tcsicmp(compareCurWordArg,_T("full")))
    {
        globalOptions::fullPath = TRUE;
    } else if (!_tcsicmp(compareCurWordArg,_T("fs32")))
    {
        globalOptions::disable64Redirector = false;
    } else if (!_tcsicmp(compareCurWordArg,_T("long")))
    {
        globalOptions::display = _T("#t #s #m  #f");
        globalOptions::summary = TRUE;
    } else if (!_tcsnicmp(compareCurWordArg,_T("limit"),5))
    {
        compareCurWordArg += 5;
        if (*compareCurWordArg == _T(':'))
            compareCurWordArg++;
        globalOptions::lineLimit = _tcstoui64(compareCurWordArg,NULL,10);
        if (!globalOptions::lineLimit)
        {
            std::wcerr << eMsg(L"Warning: You are limiting to infinity lines. Check one of your --limit options!\r\n");
        }
    } else if (!_tcsicmp(compareCurWordArg,_T("short")))
    {
        globalOptions::display = _T("#8");
    } else if (!_tcsicmp(compareCurWordArg,_T("summary")))
    {
        globalOptions::summary = TRUE;
    } else if (!_tcsicmp(compareCurWordArg,_T("norecursion")))
    {
        globalOptions::noSubDirs = TRUE;
    } else if (!_tcsnicmp(compareCurWordArg,_T("timeout"),7))
    {
        compareCurWordArg += 7;
        if (*compareCurWordArg == _T(':'))
            compareCurWordArg++;
        globalOptions::timeout = _tcstoul(compareCurWordArg,NULL,10);
        if (!globalOptions::timeout)
        {
            std::wcerr << eMsg(L"Warning: You are limiting to infinite time. Check one of your --timeout options!\r\n");
        }
    } else if (!_tcsnicmp(compareCurWordArg,_T("tx"),2))
    {
        compareCurWordArg += 2;
        if (*compareCurWordArg == _T(':'))
            compareCurWordArg++;
        globalOptions::timeout = _tcstoul(compareCurWordArg,NULL,10);
        if (!globalOptions::timeout)
        {
            std::wcerr << eMsg(L"Warning: You are limiting to infinite time. Check one of your --timeout options!\r\n");
        }
    } else
    {
        throw eMsg(L"Could not understand word argument! Ensure all of your directives are spelled correctly. Terminate.");
    }
}

Я бы выложил длинный, но это более 500 строк.

Существуют ли более эффективные способы решения этой конкретной проблемы или я должен просто оставить ее как длинный метод?

РЕДАКТИРОВАТЬ: Я не ищу библиотеку токенизации - я уже сделал грязную работу над этим. Мне любопытно, имеет ли смысл делать методы-заглушки из более крупного грязного метода.

Billy3

Ответы [ 3 ]

5 голосов
/ 20 мая 2009

Я уверен, что для Windows есть эквивалент функции getopt (3). Вот первый хит от Google - Пит Уилсон . Или вы можете заглянуть в Параметры программы Boost , чтобы найти приличную библиотеку C ++.

2 голосов
/ 20 мая 2009

Вам нужна библиотека синтаксического анализатора параметров командной строки , чтобы позаботиться о беспорядочных деталях обработки аргументов командной строки.

Я не уверен, какой из них лучше для C ++, так как я разработчик C #, который использует CSharpOptParse ... но концепция должна быть такой же, так что надеюсь, зная, что искать, укажет Вы в правильном направлении.

0 голосов
/ 22 июня 2009

HI Все, я написал этот маленький помощник для работы с командной строкой. Я также обновил его для работы с фанки: --file'thing 'и разделил по мере необходимости спрашивающему. Чтобы использовать другой тип символа, просто замените тип char тем, что вы используете. Это полностью рабочий пример, который вы можете вставить в main.cpp и запустить. Код выполняет правильное экранирование, цитирование и: and = '"в качестве разделителей имени / значения для аргументов, поэтому вы можете сделать --flag: 1 или -file" c: \ test ". Пространство примечания используется в качестве разделителя параметров. будет выглядеть примерно так, чтобы использовать его в коде:

optparse opt(argstring);<br> g_someint = strtoul(opt.get('--debuglevel','0'),0,0);<br> g_somebool = opt.get('--flag')!=0;<br> g_somestring = opt.get('--file','default.txt')

Чтобы ответить на вопрос: вы можете видеть, что это делает ваш код обработки аргументов настолько простым, что вам действительно не нужно его модульно модифицировать. Его читабельно и ремонтопригодно.

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

struct optparse{
    optparse(const char *args, size_t len = 0) : first(0) {
        size_t i;
        if(!args)args = "";
        if(!len)for(;args[len];len++);
        for(buf=new char[len+1],i=0;i<len;i++)buf[i]=args[i];buf[i]=0;
        opt *last = first;
        char *c = buf, *b = c, *v = 0, g = 0, e = 0;
        do{
            if(*c=='\\') e = e?0:1;
            else if(e?--e:1){
                if(g){ if(*c == g) g = 0; }
                else {    
                    if(*c=='"' || *c=='\''){ if(b<c && !v) v = c; g = *c; }
                    else if(!v && (*c==':' || *c=='='))    v = c; 
                    else if(*c==' '){                    
                        if(b<c)last = new opt(last,&first,b,c,v); 
                        b = c+1, v = 0;
                    }
                }
            }
            if(*c) c++;
            if(!*c && b<c) last = new opt(last,&first,b,c,v);
        }while(*c);
        for(opt *i = first; i; i = i->next) *(i->ne) = 0, *(i->ve) = 0;
    }  
    ~optparse(){
        delete buf;
        while(first){
            opt *t = first->next;
            delete first;
            first = t ;
        }
    }  

    const char *get( const char *name, const char *def= 0){
        size_t l = strlen(name);
        for(opt *i = first;i;i = i->next) if( _strnicmp( i->name, name, l ) == 0) 
            return i->value;
        return def;
    }  

    struct opt{
        opt( opt *last, opt **first, char *s, char *e, char *v){
            if(!*first) *first = this; if(last) last->next = this;
            if(v && (*v=='\'' || *v=='"') && (*(e-1)=='\'' || *(e-1) == '"'))e--;
            next = 0, name = s, value = v?v+1:"", ne = v?v:e, ve = e; 
        }
        char *name, *value, *ne, *ve;
        opt *next;
    };  
    char *buf;
    opt *first;
};  

int main(){  

    const char *v, *test ="--debug:1 -file'c:\\something' --odd=10";
    optparse opts(test);  

    if(v = opts.get("--debug")){
       printf("debug flag value is %s\n",v);
    }  

    for(optparse::opt *i=opts.first;i;i=i->next){
        printf("name: %s value: %s\n",i->name,i->value);
    }  
}

Парсер очень легко настраивается для поддержки различных типов обработки аргументов. Например, если вы замените

if(b<c)last = new opt(last,&first,b,c,v); 
b = c+1, v = 0;

с

if(*b=='-' && *(c+1)!='-')v = v?v:c;
else{
   if(b<c)last = new opt(last,&first,b,c,v); 
   b = c+1, v = 0;
}

Вы добавите возможность объединения аргументов, разделенных пробелом, в виде «значения», например: -debug 1 или --files a.txt b.txt c.txt Также, если вам не нравится параметр: как разделенный параметр (это может быть неудобно в приложениях для Windows), просто удалите == ':'

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...