我为我正在开发的这个图书馆编写了一个小型命令行助手工具。该库为虚拟变形在iOS设备上的使用提供了工具(主要是游戏)。
这个小小的命令行助手仍然是我尽可能快编写的一个原型,用于测试一个新的文件格式。在我进一步扩展之前,请提供一些反馈意见:
// 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。
发布于 2014-11-23 04:45:03
标准约定表示输入和输出文件排在最后。
标志是首先出现的修饰符。
输入和输出都应该是可选的,如果不提供,则替换为标准的输入/输出。注意,-本身而不是标志应该指示标准输入/输出,这取决于它的位置。
因此,我会这样写:
vtmake [--flags=] [<input_file> [<output_file>]]示例用法:
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
发布于 2014-11-23 02:56:17
vtmake中硬编码printHelpAndExit打印argv[0]。parseWhatever函数都有自己的代码副本,查找=。必须将其分解为函数;更好的是,使用strchr。我还建议重新构造代码,让parseCmdLine找到=。parseCmdLine中的循环包含太多的代码。我希望有一张解析函数的选项图。类似地,我将有一个过滤器名称到vt::tool::FilterType值的映射。cmdLineOpts, inputFile, outputFile需要存在于命名空间范围中。我建议在main中定义它们,并将它们作为参数传递给感兴趣的各方。getopt呢?PageFileBuilder看起来像generatePageFile的实现细节,不应该向客户端公开。发布于 2014-11-23 01:31:39
为了组织目的,我可以看到将一些可重用的解析内容放入一个单独的名称空间(带有它自己的头/cxx文件),如果您认为以后可能需要它用于另一个项目的话。总的来说,这看起来真的很不错。
https://codereview.stackexchange.com/questions/70620
复制相似问题