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" }));