From 73eb7c236bc76f8e548a25fd87b2c075226a8373 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Wed, 28 Aug 2024 11:19:33 +0200 Subject: [PATCH] Port zimcheck to docoptcpp We don't have getopt on Windows. Let's move command line parsing to docoptcpp as we already use it. We lost parsing case insensitive options on the way. --- src/zimcheck/meson.build | 2 +- src/zimcheck/zimcheck.cpp | 250 +++++++++++++++----------------------- test/meson.build | 2 +- test/zimcheck-test.cpp | 111 ++++++++--------- 4 files changed, 149 insertions(+), 216 deletions(-) diff --git a/src/zimcheck/meson.build b/src/zimcheck/meson.build index 917e76c1..6871df71 100644 --- a/src/zimcheck/meson.build +++ b/src/zimcheck/meson.build @@ -23,7 +23,7 @@ executable('zimcheck', '../tools.cpp', '../metadata.cpp', include_directories : inc, - dependencies: [libzim_dep, icu_dep, thread_dep], + dependencies: [libzim_dep, icu_dep, thread_dep, docopt_dep], install: true) diff --git a/src/zimcheck/zimcheck.cpp b/src/zimcheck/zimcheck.cpp index 2703c923..56276192 100644 --- a/src/zimcheck/zimcheck.cpp +++ b/src/zimcheck/zimcheck.cpp @@ -21,7 +21,7 @@ */ #include -#include +#include #include #include #include @@ -33,6 +33,7 @@ #include #include #include +#include #ifndef _WIN32 #include @@ -43,36 +44,37 @@ #include "../tools.h" #include "checks.h" -void displayHelp() -{ - std::cout<<"\n" - "zimcheck checks the quality of a ZIM file.\n\n" - "Usage: zimcheck [options] zimfile\n" - "options:\n" - "-A , --all run all tests. Default if no flags are given.\n" - "-0 , --empty Empty content\n" - "-C , --checksum Internal CheckSum Test\n" - "-I , --integrity Low-level correctness/integrity checks\n" - "-M , --metadata MetaData Entries\n" - "-F , --favicon Favicon\n" - "-P , --main Main page\n" - "-R , --redundant Redundant data check\n" - "-U , --url_internal URL check - Internal URLs\n" - "-X , --url_external URL check - External URLs\n" - "-D , --details Details of error\n" - "-B , --progress Print progress report\n" - "-J , --json Output in JSON format\n" - "-H , --help Displays Help\n" - "-V , --version Displays software version\n" - "-L , --redirect_loop Checks for the existence of redirect loops\n" - "-W , --threads count of threads to utilize (default: 1)\n" - "examples:\n" - "zimcheck -A wikipedia.zim\n" - "zimcheck --checksum --redundant wikipedia.zim\n" - "zimcheck -F -R wikipedia.zim\n" - "zimcheck -M --favicon wikipedia.zim\n"; - return; -} +static const char USAGE[] = +R"(Zimcheck checks the quality of a ZIM file. + +Usage: + zimcheck [options] [ZIMFILE] + +Options: + -a --all run all tests. Default if no flags are given. + -0 --empty Empty content + -c --checksum Internal CheckSum Test + -i --integrity Low-level correctness/integrity checks + -m --metadata MetaData Entries + -f --favicon Favicon + -p --main Main page + -r --redundant Redundant data check + -u --url_internal URL check - Internal URLs + -x --url_external URL check - External URLs + -d --details Details of error + -b --progress Print progress report + -j --json Output in JSON format + -h --help Displays Help + -v --version Displays software version + -l --redirect_loop Checks for the existence of redirect loops + -w= --threads= count of threads to utilize [default: 1] + +Examples: + zimcheck -a wikipedia.zim + zimcheck --checksum --redundant wikipedia.zim + zimcheck -f -r wikipedia.zim + zimcheck -m --favicon wikipedia.zim)"; + template std::string stringify(const T& x) @@ -82,11 +84,36 @@ std::string stringify(const T& x) return ss.str(); } -int zimcheck (const std::vector& args) -{ - const int argc = args.size(); - const char* const* argv = &args[0]; +int zimcheck(const std::map& args); + +int zimcheck(const std::vector& args) { + std::vector args_string; + bool first = true; + for (auto arg:args) { + if (first) { + first = false; + continue; + } + args_string.emplace_back(arg); + } + docopt::Options parsed_args; + try { + parsed_args = docopt::docopt_parse( + USAGE, + args_string, + false, + false); + } catch (docopt::DocoptArgumentError const& error) { + std::cerr << error.what() << std::endl; + std::cout << USAGE << std::endl; + return 1; + } + return zimcheck(parsed_args); +} + +int zimcheck(const docopt::Options& args) +{ // To calculate the total time taken by the program to run. const auto starttime = std::chrono::steady_clock::now(); @@ -99,140 +126,69 @@ int zimcheck (const std::vector& args) bool error_details = false; bool no_args = true; bool json = false; - bool help = false; int thread_count = 1; std::string filename = ""; ProgressBar progress(1); StatusCode status_code = PASS; - - //Parsing through arguments using getopt_long(). Both long and short arguments are allowed. - optind = 1; // reset getopt_long(), so that zimcheck() works correctly if - // called more than once - opterr = 0; // silence getopt_long() - while (1) - { - static struct option long_options[] = - { - { "all", no_argument, 0, 'A'}, - { "progress", no_argument, 0, 'B'}, - { "empty", no_argument, 0, '0'}, - { "checksum", no_argument, 0, 'C'}, - { "integrity", no_argument, 0, 'I'}, - { "metadata", no_argument, 0, 'M'}, - { "favicon", no_argument, 0, 'F'}, - { "main", no_argument, 0, 'P'}, - { "redundant", no_argument, 0, 'R'}, - { "url_internal", no_argument, 0, 'U'}, - { "url_external", no_argument, 0, 'X'}, - { "details", no_argument, 0, 'D'}, - { "json", no_argument, 0, 'J'}, - { "threads", required_argument, 0, 'w'}, - { "help", no_argument, 0, 'H'}, - { "version", no_argument, 0, 'V'}, - { "redirect_loop",no_argument, 0, 'L'}, - { 0, 0, 0, 0} - }; - int option_index = 0; - int c = getopt_long (argc, const_cast(argv), "ACIJMFPRUXLEDHBVW:acijmfpruxledhbvw:0", - long_options, &option_index); - //c = getopt (argc, argv, "ACMFPRUXED"); - if(c == -1) - break; - switch (c) - { - case 'A': - case 'a': + + for(auto const& arg: args) { + if (arg.first == "--all" && arg.second.asBool()) { run_all = true; no_args = false; - break; - case '0': + } else if (arg.first == "--help" && arg.second.asBool()) { + std::cout << USAGE << std::endl; + return -1; + } else if (arg.first == "--empty" && arg.second.asBool()) { enabled_tests.enable(TestType::EMPTY); no_args = false; - break; - case 'C': - case 'c': + } else if (arg.first == "--checksum" && arg.second.asBool()) { enabled_tests.enable(TestType::CHECKSUM); no_args = false; - break; - case 'I': - case 'i': + } else if (arg.first == "--integrity" && arg.second.asBool()) { enabled_tests.enable(TestType::INTEGRITY); no_args = false; - break; - case 'M': - case 'm': + } else if (arg.first == "--metadata" && arg.second.asBool()) { enabled_tests.enable(TestType::METADATA); no_args = false; - break; - case 'B': - case 'b': - progress.set_progress_report(true); - break; - case 'F': - case 'f': + } else if (arg.first == "--progress") { + progress.set_progress_report(arg.second.asBool()); + } else if (arg.first == "--favicon" && arg.second.asBool()) { enabled_tests.enable(TestType::FAVICON); no_args = false; - break; - case 'P': - case 'p': + } else if (arg.first == "--main" && arg.second.asBool()) { enabled_tests.enable(TestType::MAIN_PAGE); no_args = false; - break; - case 'R': - case 'r': + } else if (arg.first == "--redundant" && arg.second.asBool()) { enabled_tests.enable(TestType::REDUNDANT); no_args = false; - break; - case 'U': - case 'u': + } else if (arg.first == "--url_internal" && arg.second.asBool()) { enabled_tests.enable(TestType::URL_INTERNAL); no_args = false; - break; - case 'X': - case 'x': + } else if (arg.first == "--url_external" && arg.second.asBool()) { enabled_tests.enable(TestType::URL_EXTERNAL); no_args = false; - break; - case 'L': - case 'l': + } else if (arg.first == "--redirect_loop" && arg.second.asBool()) { enabled_tests.enable(TestType::REDIRECT); no_args = false; - break; - case 'D': - case 'd': - error_details = true; - break; - case 'J': - case 'j': - json = true; - break; - case 'W': - case 'w': - thread_count = atoi(optarg); - break; - case 'H': - case 'h': - help=true; - break; - case '?': - std::cerr<<"Unknown option `" << argv[optind-1] << "'\n"; - displayHelp(); - return 1; - case 'V': - case 'v': - printVersions(); - return 0; - default: - abort (); + } else if (arg.first == "--details") { + error_details = arg.second.asBool(); + } else if (arg.first == "--json") { + json = arg.second.asBool(); + } else if (arg.first == "--threads") { + thread_count = arg.second.asLong(); + } else if (arg.first == "ZIMFILE" && arg.second.isString()) { + filename = arg.second.asString(); + } else if (arg.first == "--version" && arg.second.asBool()) { + printVersions(); + return 0; } } - - //Displaying Help for --help argument - if(help) - { - displayHelp(); + + if (filename.empty()) { + std::cerr << "No file provided as argument" << std::endl; + std::cout << USAGE << std::endl; return -1; } @@ -242,22 +198,6 @@ int zimcheck (const std::vector& args) enabled_tests.enableAll(); } - //Obtaining filename from argument list - filename = ""; - for(int i = 0; i < argc; i++) - { - if( (argv[i][0] != '-') && (i != 0)) - { - filename = argv[i]; - } - } - if(filename == "") - { - std::cerr<<"No file provided as argument\n"; - displayHelp(); - return -1; - } - ErrorLogger error(json); error.addInfo("zimcheck_version", std::string(VERSION)); //Tests. diff --git a/test/meson.build b/test/meson.build index 704aba53..5b437c73 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,7 +1,7 @@ gtest_dep = dependency('gtest', main:true, fallback:['gtest', 'gtest_main_dep'], required:false) -test_deps = [gtest_dep, libzim_dep, icu_dep] +test_deps = [gtest_dep, libzim_dep, icu_dep, docopt_dep] tests = [ 'metadata-test', 'zimcheck-test' diff --git a/test/zimcheck-test.cpp b/test/zimcheck-test.cpp index cd5b497a..e75b1588 100644 --- a/test/zimcheck-test.cpp +++ b/test/zimcheck-test.cpp @@ -131,33 +131,36 @@ struct CapturedStderr : CapturedStdStream int zimcheck (const std::vector& args); const std::string zimcheck_help_message( - "\n" - "zimcheck checks the quality of a ZIM file.\n\n" - "Usage: zimcheck [options] zimfile\n" - "options:\n" - "-A , --all run all tests. Default if no flags are given.\n" - "-0 , --empty Empty content\n" - "-C , --checksum Internal CheckSum Test\n" - "-I , --integrity Low-level correctness/integrity checks\n" - "-M , --metadata MetaData Entries\n" - "-F , --favicon Favicon\n" - "-P , --main Main page\n" - "-R , --redundant Redundant data check\n" - "-U , --url_internal URL check - Internal URLs\n" - "-X , --url_external URL check - External URLs\n" - "-D , --details Details of error\n" - "-B , --progress Print progress report\n" - "-J , --json Output in JSON format\n" - "-H , --help Displays Help\n" - "-V , --version Displays software version\n" - "-L , --redirect_loop Checks for the existence of redirect loops\n" - "-W , --threads count of threads to utilize (default: 1)\n" - "examples:\n" - "zimcheck -A wikipedia.zim\n" - "zimcheck --checksum --redundant wikipedia.zim\n" - "zimcheck -F -R wikipedia.zim\n" - "zimcheck -M --favicon wikipedia.zim\n" -); +R"(Zimcheck checks the quality of a ZIM file. + +Usage: + zimcheck [options] [ZIMFILE] + +Options: + -a --all run all tests. Default if no flags are given. + -0 --empty Empty content + -c --checksum Internal CheckSum Test + -i --integrity Low-level correctness/integrity checks + -m --metadata MetaData Entries + -f --favicon Favicon + -p --main Main page + -r --redundant Redundant data check + -u --url_internal URL check - Internal URLs + -x --url_external URL check - External URLs + -d --details Details of error + -b --progress Print progress report + -j --json Output in JSON format + -h --help Displays Help + -v --version Displays software version + -l --redirect_loop Checks for the existence of redirect loops + -w= --threads= count of threads to utilize [default: 1] + +Examples: + zimcheck -a wikipedia.zim + zimcheck --checksum --redundant wikipedia.zim + zimcheck -f -r wikipedia.zim + zimcheck -m --favicon wikipedia.zim +)"); TEST(zimcheck, help) { @@ -166,11 +169,6 @@ TEST(zimcheck, help) ASSERT_EQ(-1, zimcheck({"zimcheck", "-h"})); ASSERT_EQ(zimcheck_help_message, std::string(zimcheck_output)); } - { - CapturedStdout zimcheck_output; - ASSERT_EQ(-1, zimcheck({"zimcheck", "-H"})); - ASSERT_EQ(zimcheck_help_message, std::string(zimcheck_output)); - } { CapturedStdout zimcheck_output; ASSERT_EQ(-1, zimcheck({"zimcheck", "--help"})); @@ -187,11 +185,6 @@ TEST(zimcheck, version) ASSERT_EQ(0, zimcheck({"zimcheck", "-v"})); ASSERT_EQ(version, getLine(std::string(zimcheck_output))); } - { - CapturedStdout zimcheck_output; - ASSERT_EQ(0, zimcheck({"zimcheck", "-V"})); - ASSERT_EQ(version, getLine(std::string(zimcheck_output))); - } { CapturedStdout zimcheck_output; ASSERT_EQ(0, zimcheck({"zimcheck", "--version"})); @@ -261,7 +254,7 @@ TEST(zimcheck, integrity_goodzimfile) ); test_zimcheck_single_option( - {"-i", "-I", "--integrity"}, + {"-i", "--integrity"}, GOOD_ZIMFILE, 0, expected_output, @@ -281,7 +274,7 @@ TEST(zimcheck, checksum_goodzimfile) ); test_zimcheck_single_option( - {"-c", "-C", "--checksum"}, + {"-c", "--checksum"}, GOOD_ZIMFILE, 0, expected_output, @@ -301,7 +294,7 @@ TEST(zimcheck, metadata_goodzimfile) ); test_zimcheck_single_option( - {"-m", "-M", "--metadata"}, + {"-m", "--metadata"}, GOOD_ZIMFILE, 0, expected_output, @@ -321,7 +314,7 @@ TEST(zimcheck, favicon_goodzimfile) ); test_zimcheck_single_option( - {"-f", "-F", "--favicon"}, + {"-f", "--favicon"}, GOOD_ZIMFILE, 0, expected_output, @@ -341,7 +334,7 @@ TEST(zimcheck, mainpage_goodzimfile) ); test_zimcheck_single_option( - {"-p", "-P", "--main"}, + {"-p", "--main"}, GOOD_ZIMFILE, 0, expected_output, @@ -363,8 +356,8 @@ TEST(zimcheck, article_content_goodzimfile) test_zimcheck_single_option( { "-0", "--empty", // Any of these options triggers - "-u", "-U", "--url_internal", // checking of the article contents. - "-x", "-X", "--url_external" // For a good ZIM file there is no + "-u", "--url_internal", // checking of the article contents. + "-x", "--url_external" // For a good ZIM file there is no }, // difference in the output. GOOD_ZIMFILE, 0, @@ -387,7 +380,7 @@ TEST(zimcheck, redundant_articles_goodzimfile) ); test_zimcheck_single_option( - {"-r", "-R", "--redundant"}, + {"-r", "--redundant"}, GOOD_ZIMFILE, 0, expected_output, @@ -407,7 +400,7 @@ TEST(zimcheck, redirect_loop_goodzimfile) ); test_zimcheck_single_option( - {"-l", "-L", "--redirect_loop"}, + {"-l", "--redirect_loop"}, GOOD_ZIMFILE, 0, expected_output, @@ -442,7 +435,7 @@ TEST(zimcheck, nooptions_goodzimfile) TEST(zimcheck, all_checks_goodzimfile) { test_zimcheck_single_option( - {"-a", "-A", "--all"}, + {"-a", "--all"}, GOOD_ZIMFILE, 0, ALL_CHECKS_OUTPUT_ON_GOODZIMFILE, @@ -456,7 +449,7 @@ TEST(zimcheck, invalid_option) CapturedStdout zimcheck_output; CapturedStderr zimcheck_stderr; ASSERT_EQ(1, zimcheck({"zimcheck", "-z", GOOD_ZIMFILE})); - ASSERT_EQ("Unknown option `-z'\n", std::string(zimcheck_stderr)); + ASSERT_EQ("Unexpected argument: -z, data/zimfiles/good.zim\n", std::string(zimcheck_stderr)); ASSERT_EQ(zimcheck_help_message, std::string(zimcheck_output)); } } @@ -467,7 +460,7 @@ TEST(zimcheck, invalid_long_option) CapturedStdout zimcheck_output; CapturedStderr zimcheck_stderr; ASSERT_EQ(1, zimcheck({"zimcheck", "--oops", GOOD_ZIMFILE})); - ASSERT_EQ("Unknown option `--oops'\n", std::string(zimcheck_stderr)); + ASSERT_EQ("Unexpected argument: --oops, data/zimfiles/good.zim\n", std::string(zimcheck_stderr)); ASSERT_EQ(zimcheck_help_message, std::string(zimcheck_output)); } } @@ -523,7 +516,7 @@ TEST(zimcheck, bad_checksum) ); test_zimcheck_single_option( - {"-c", "-C", "--checksum"}, + {"-c", "--checksum"}, BAD_CHECKSUM_ZIMFILE, 1, expected_output, @@ -549,7 +542,7 @@ TEST(zimcheck, metadata_poorzimfile) ); test_zimcheck_single_option( - {"-m", "-M", "--metadata"}, + {"-m", "--metadata"}, POOR_ZIMFILE, 1, expected_stdout, @@ -571,7 +564,7 @@ TEST(zimcheck, favicon_poorzimfile) ); test_zimcheck_single_option( - {"-f", "-F", "--favicon"}, + {"-f", "--favicon"}, POOR_ZIMFILE, 1, expected_stdout, @@ -593,7 +586,7 @@ TEST(zimcheck, mainpage_poorzimfile) ); test_zimcheck_single_option( - {"-p", "-P", "--main"}, + {"-p", "--main"}, POOR_ZIMFILE, 1, expected_stdout, @@ -642,7 +635,7 @@ TEST(zimcheck, internal_url_check_poorzimfile) ); test_zimcheck_single_option( - {"-u", "-U", "--url_internal"}, + {"-u", "--url_internal"}, POOR_ZIMFILE, 1, expected_stdout, @@ -666,7 +659,7 @@ TEST(zimcheck, external_url_check_poorzimfile) ); test_zimcheck_single_option( - {"-x", "-X", "--url_external"}, + {"-x", "--url_external"}, POOR_ZIMFILE, 1, expected_stdout, @@ -690,7 +683,7 @@ TEST(zimcheck, redundant_poorzimfile) ); test_zimcheck_single_option( - {"-r", "-R", "--redundant"}, + {"-r", "--redundant"}, POOR_ZIMFILE, 0, expected_stdout, @@ -717,7 +710,7 @@ TEST(zimcheck, redirect_loop_poorzimfile) ); test_zimcheck_single_option( - {"-l", "-L", "--redirect_loop"}, + {"-l", "--redirect_loop"}, POOR_ZIMFILE, 1, expected_output, @@ -784,7 +777,7 @@ TEST(zimcheck, nooptions_poorzimfile) TEST(zimcheck, all_checks_poorzimfile) { test_zimcheck_single_option( - {"-a", "-A", "--all"}, + {"-a", "--all"}, POOR_ZIMFILE, 1, ALL_CHECKS_OUTPUT_ON_POORZIMFILE, @@ -798,7 +791,7 @@ TEST(zimcheck, json_bad_checksum) ASSERT_EQ(1, zimcheck({ "zimcheck", "--json", - "-C", + "-c", "data/zimfiles/bad_checksum.zim" }));