首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >小型命令行辅助工具

小型命令行辅助工具
EN

Code Review用户
提问于 2014-11-22 23:48:59
回答 3查看 296关注 0票数 5

我为我正在开发的这个图书馆编写了一个小型命令行助手工具。该库为虚拟变形在iOS设备上的使用提供了工具(主要是游戏)。

这个小小的命令行助手仍然是我尽可能快编写的一个原型,用于测试一个新的文件格式。在我进一步扩展之前,请提供一些反馈意见:

代码语言:javascript
复制
// Virtual Texturing Library:
#include "vt_tool_image.hpp"
#include "vt_tool_pagefile_builder.hpp"
#include "vt_tool_platform_utils.hpp"

// Standard library:
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>

namespace {

// ======================================================
// Local data:
// ======================================================

vt::tool::PageFileBuilderOptions cmdLineOpts;
std::string inputFile, outputFile;

// ======================================================
// printHelpAndExit()
// ======================================================

void printHelpAndExit()
{
    std::printf("\n"
    "Usage:\n"
    "$ vtmake <input_file> <output_file> [--flags=]\n"
    "\n"
    "Flags accepted:\n"
    "--help           : prints help text with list of commands.\n"
    "--filter         : (str)  type of mipmapping filter: box, tri, quad, cubic, bspline, mitchell, lanczos, sinc, kaiser.\n"
    "--page_size      : (int)  total page size in pixels, including border.\n"
    "--content_size   : (int)  size in pixels of page content, not including border.\n"
    "--border_size    : (int)  size in pixels of the page border.\n"
    "--max_levels     : (int)  max mipmap levels to generate.\n"
    "--flip_v_src     : (bool) flip the source image vertically.\n"
    "--flip_v_tiles   : (bool) flip each individual tile/page vertically.\n"
    "--stop_on_1_mip  : (bool) stop subdividing when mip 0 is reached.\n"
    "--add_debug_info : (bool) print debug text to each page.\n"
    "--dump_images    : (bool) dump each page as an image file (TGA format).\n"
    "--verbose        : (bool) print stuff to STDOUT while running.\n"
    "\n");
    std::exit(0);
}

// ======================================================
// error():
// ======================================================

void error(const char * format, ...)
{
    va_list vaList;
    char buffer[1024];

    va_start(vaList, format);
    std::vsnprintf(buffer, sizeof(buffer), format, vaList);
    va_end(vaList);

    buffer[sizeof(buffer) - 1] = '\0'; // Ensure a null at the end
    throw vt::tool::PageFileBuilderError(buffer);
}

// ======================================================
// parseFilterName():
// ======================================================

vt::tool::FilterType parseFilterName(const char * str)
{
    // Find the value after the '=' sign, if any:
    while ((*str != '=') && (*str != '\0'))
    {
        ++str;
    }
    if (*str == '=') { ++str; }

    if (std::strcmp(str, "box"     ) == 0) { return vt::tool::FilterType::Box;       }
    if (std::strcmp(str, "tri"     ) == 0) { return vt::tool::FilterType::Triangle;  }
    if (std::strcmp(str, "quad"    ) == 0) { return vt::tool::FilterType::Quadratic; }
    if (std::strcmp(str, "cubic"   ) == 0) { return vt::tool::FilterType::Cubic;     }
    if (std::strcmp(str, "bspline" ) == 0) { return vt::tool::FilterType::BSpline;   }
    if (std::strcmp(str, "mitchell") == 0) { return vt::tool::FilterType::Mitchell;  }
    if (std::strcmp(str, "lanczos" ) == 0) { return vt::tool::FilterType::Lanczos;   }
    if (std::strcmp(str, "sinc"    ) == 0) { return vt::tool::FilterType::Sinc;      }
    if (std::strcmp(str, "kaiser"  ) == 0) { return vt::tool::FilterType::Kaiser;    }

    std::printf("WARNING: Unknown filter '%s'! Defaulting to box filter.\n", str);
    return vt::tool::FilterType::Box;
}

// ======================================================
// parseInt():
// ======================================================

int parseInt(const char * str)
{
    // Find the value after the '=' sign, if any:
    while ((*str != '=') && (*str != '\0'))
    {
        ++str;
    }
    if (*str == '=') { ++str; }
    return std::stoi(str);
}

// ======================================================
// parseBool():
// ======================================================

bool parseBool(const char * str)
{
    // Find the value after the '=' sign, if any:
    while ((*str != '=') && (*str != '\0'))
    {
        ++str;
    }
    if (*str == '=') { ++str; }

    if ((std::strcmp(str, "false") == 0) ||
        (std::strcmp(str, "no")    == 0) ||
        (std::strcmp(str, "0")     == 0))
    {
        return false;
    }

    // Assume true for anything else, including an invalid value or an empty string.
    // (results in true for "--flag" with no "=value" part)
    return true;
}

// ======================================================
// startsWith():
// ======================================================

bool startsWith(const char * str, const char * prefix)
{
    const size_t prefixLen = std::strlen(prefix);
    if (prefixLen == 0)
    {
        return false;
    }
    return std::strncmp(str, prefix, prefixLen) == 0;
}

// ======================================================
// parseCmdLine():
// ======================================================

void parseCmdLine(const int argc, const char * argv[])
{
    // Possible "--help" call
    if ((argc == 2) && startsWith(argv[1], "--help"))
    {
        printHelpAndExit();
    }

    // Must have at least argv[0], in_file and out_file
    if (argc < 3)
    {
        error("Not enough arguments!");
    }

    /* argc[0] == "vtmake" (prog name) */
    inputFile  = argv[1];
    outputFile = argv[2];

    for (int i = 3; i < argc; ++i)
    {
        if (startsWith(argv[i], "--help"))
        {
            printHelpAndExit();
        }
        else if (startsWith(argv[i], "--filter"))
        {
            cmdLineOpts.textureFilter = parseFilterName(argv[i]);
        }
        else if (startsWith(argv[i], "--page_size"))
        {
            cmdLineOpts.pageSizePixels = parseInt(argv[i]);
        }
        else if (startsWith(argv[i], "--content_size"))
        {
            cmdLineOpts.pageContentSizePixels = parseInt(argv[i]);
        }
        else if (startsWith(argv[i], "--border_size"))
        {
            cmdLineOpts.pageBorderSizePixels = parseInt(argv[i]);
        }
        else if (startsWith(argv[i], "--max_levels"))
        {
            cmdLineOpts.maxMipLevels = parseInt(argv[i]);
        }
        else if (startsWith(argv[i], "--flip_v_src"))
        {
            cmdLineOpts.flipSourceVertically = parseBool(argv[i]);
        }
        else if (startsWith(argv[i], "--flip_v_tiles"))
        {
            cmdLineOpts.flipTilesVertically = parseBool(argv[i]);
        }
        else if (startsWith(argv[i], "--stop_on_1_mip"))
        {
            cmdLineOpts.stopOn1PageMip = parseBool(argv[i]);
        }
        else if (startsWith(argv[i], "--add_debug_info"))
        {
            cmdLineOpts.addDebugInfoToPages = parseBool(argv[i]);
        }
        else if (startsWith(argv[i], "--dump_images"))
        {
            cmdLineOpts.dumpPageImages = parseBool(argv[i]);
        }
        else if (startsWith(argv[i], "--verbose"))
        {
            cmdLineOpts.stdoutVerbose = parseBool(argv[i]);
        }
        else
        {
            std::printf("WARNING: Unknown command line argument: '%s'\n", argv[i]);
        }
    }

    if (cmdLineOpts.stdoutVerbose)
    {
        std::printf("Input  file: \"%s\"\n", inputFile.c_str());
        std::printf("Output file: \"%s\"\n", outputFile.c_str());
        cmdLineOpts.printSelf();
    }
}

// ======================================================
// runPageFileBuilder():
// ======================================================

void runPageFileBuilder()
{
    if (inputFile.empty())
    {
        error("No input filename!");
    }
    if (outputFile.empty())
    {
        error("No output filename!");
    }

    vt::tool::PageFileBuilder pageFileBuilder(inputFile, outputFile, cmdLineOpts);
    pageFileBuilder.generatePageFile();
    std::printf("Done!\n");
}

} // namespace {}

// ======================================================
// main():
// ======================================================

int main(int argc, const char * argv[])
{
    try
    {
        parseCmdLine(argc, argv);
        runPageFileBuilder();
        return 0;
    }
    catch (std::exception & e)
    {
        std::printf("ERROR: %s\n", e.what());
        return -1;
    }
}

如您所见,这段代码的主要目的是解析和验证命令行args。然后由图书馆来完成这项繁重的工作。

我承认这看上去很冷酷。因为我为测试而快速地写了它,所以我没费什么劲。此外,命令args验证仍然相当薄弱。我可能会考虑将它重构成一个类,并使用更少的char*和更多的std::string

EN

回答 3

Code Review用户

回答已采纳

发布于 2014-11-23 04:45:03

标准约定表示输入和输出文件排在最后。

标志是首先出现的修饰符。

输入和输出都应该是可选的,如果不提供,则替换为标准的输入/输出。注意,-本身而不是标志应该指示标准输入/输出,这取决于它的位置。

因此,我会这样写:

代码语言:javascript
复制
vtmake [--flags=] [<input_file> [<output_file>]]

示例用法:

代码语言:javascript
复制
vtmake                   // Reads standard input writes to standard output
vtmake X                 // Reads X writes to standard output
vtmake - Y               // Reads standard input writes to Y
vtmake X Y               // Reads X writes to Y

还有一些非常好的命令行解析实用程序(所以您不必编写带有潜在错误的样板代码)。当有人为您编写和调试时,您可能应该使用它们。

http://www.gnu.org/software/libc/manual/html_节点/解析-Program-Arguments.html

票数 2
EN

Code Review用户

发布于 2014-11-23 02:56:17

  • 不要在vtmake中硬编码printHelpAndExit打印argv[0]
  • 每个parseWhatever函数都有自己的代码副本,查找=。必须将其分解为函数;更好的是,使用strchr。我还建议重新构造代码,让parseCmdLine找到=
  • parseCmdLine中的循环包含太多的代码。我希望有一张解析函数的选项图。类似地,我将有一个过滤器名称到vt::tool::FilterType值的映射。
  • 整个命令行结构非常非常规。通常,位置参数遵循选项。此外,使文件名也是可选的也是非常有用的:这样的工具就可以在管道中使用。
  • 我不认为cmdLineOpts, inputFile, outputFile需要存在于命名空间范围中。我建议在main中定义它们,并将它们作为参数传递给感兴趣的各方。
  • 最后,为什么不直接使用getopt呢?
  • 附带注意: vt::tool::PageFileBuilder pageFileBuilder(inputFile,outputFile,cmdLineOpts);pageFileBuilder.generatePageFile();似乎不正确。PageFileBuilder看起来像generatePageFile的实现细节,不应该向客户端公开。
票数 3
EN

Code Review用户

发布于 2014-11-23 01:31:39

为了组织目的,我可以看到将一些可重用的解析内容放入一个单独的名称空间(带有它自己的头/cxx文件),如果您认为以后可能需要它用于另一个项目的话。总的来说,这看起来真的很不错。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/70620

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档