【问题标题】:Break apart long commandline testing method拆分长命令行测试方法
【发布时间】:2009-05-20 00:38:44
【问题描述】:

我有一个大型(阅读:噩梦)方法,它多年来一直在增长,以支持我的项目不断增长的命令行参数列表。我的意思是几页自述文档,用于每个参数的简短说明。

当我添加了每个功能时,我只是通过在该方法中添加几行来“注册”一种处理该参数的方法。

但是,该方法现在不美观、容易出错且难以理解。这是当前处理此问题的两种方法中较短的一个示例:

//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 行。

有没有更好的方法来处理这个特定的问题,还是我应该把它作为一个长方法?

编辑:我不是在寻找一个标记化库——我已经在这方面做了一些肮脏的工作。我很好奇从更大的脏方法中制作存根方法是否有意义。

比利3

【问题讨论】:

    标签: c++ methods


    【解决方案1】:

    我确信在 Windows 上有一个等效的 getopt(3) 函数。 这是来自 Google 的第一次点击 - Pete Wilson。 或者您可以查看Boost Program Options 以获得一个不错的 C++ 库。

    【讨论】:

    • boost 库(我已经在使用 boost)是否支持将以下内容作为单个参数读取? [ -files"C:\Documents and Settings\User\Desktop\InFile.txt" ] 请注意如何不引用标记,并且确实包含空格。引号从令牌本身开始。
    • 试试看,使用起来真的很简单。
    • 试过了 .. 在我目前的情况下不起作用。项目在命令行上实现了一个递归下降解析器。例如:progname C:\Windows\* AND -tf OR *.dll OR *.exe 打印窗口中所有文件(不是文件夹)、.dll 或 .exe 文件。 boost 库是围绕成对出现的选项设计的——它对我的应用程序来说不够复杂。对不起。
    • 好的,我没有从原始问题中得到您的要求 - 您在谈论选项,而不是语言。另一方面,递归下降解析并不是那么难,但定义(或至少写下)你的语法确实是值得的。我更喜欢编写正确的(即使是简单的)词法分析器和解析器,并为类似的东西构建 AST。
    【解决方案2】:

    您需要的是 command line option parser library 来处理处理命令行参数的杂乱细节。

    我不确定哪一个最适合 C++,因为我是使用 CSharpOptParse 的 C# 开发人员...但是概念应该是相同的,所以希望知道要查找的内容会为您指明正确的方向方向。

    【讨论】:

    • 不确定我能做到这一点,因为我的应用程序支持一些非常奇怪的命令行。例如,这是完全有效的: -files"C:\Documents and Settings\User\Desktop\InFile.txt" 请注意如何以非标准方式处理引号并将其视为一个标记。我想我的问题更多的是结构性问题。
    【解决方案3】:

    大家好,我为使用命令行编写了这个小助手。我还更新了它以使用时髦的:--file'thing' 并根据提问者的需要进行拆分。 要使其使用另一种字符类型,只需将 char 类型替换为您正在使用的字符类型。这是一个完整的示例,您可以粘贴到 main.cpp 中并运行。 该代码执行正确的转义、引用分组和 : 和 = '" 作为 args 的名称/值拆分器,因此您可以执行 --flag:1 或 -file"c:\test"。请注意空格用作选项拆分器。它在代码中使用它看起来像这样:

    optparse opt(argstring);
    g_someint = strtoul(opt.get('--debuglevel','0'),0,0);
    g_somebool = opt.get('--flag')!=0;
    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 应用程序中可能很麻烦),只需删除 ==':'

    【讨论】:

      猜你喜欢
      • 2020-05-14
      • 1970-01-01
      • 2011-02-22
      • 2020-04-28
      • 1970-01-01
      • 2011-02-06
      • 2018-05-07
      • 1970-01-01
      • 2013-05-19
      相关资源
      最近更新 更多