diff --git a/src/cli.cc b/src/cli.cc index 84a66a0..8a69098 100644 --- a/src/cli.cc +++ b/src/cli.cc @@ -1,33 +1,66 @@ +#include #include #include #include +static const char* version = PROJECT_NAME " version " PROJECT_VERSION "\n"; + +static const char* usage = 1 + R"raw( +Usage: opustags --help + opustags [OPTIONS] FILE + opustags OPTIONS FILE -o FILE +)raw"; + +static const char* help = 1 + R"raw( +Options: + -h, --help print this help + -o, --output write the modified tags to a file + -i, --in-place [SUFFIX] use a temporary file then replace the original file + -y, --overwrite overwrite the output file if it already exists + -d, --delete FIELD delete all the fields of a specified type + -a, --add FIELD=VALUE add a field + -s, --set FIELD=VALUE delete then add a field + -D, --delete-all delete all the fields! + -S, --set-all read the fields from stdin +)raw"; + static struct option getopt_options[] = { - {"help", no_argument, 0, 'h'}, - {"output", required_argument, 0, 'o'}, - {"in-place", optional_argument, 0, 'i'}, - {"overwrite", no_argument, 0, 'y'}, - {"delete", required_argument, 0, 'd'}, - {"add", required_argument, 0, 'a'}, - {"set", required_argument, 0, 's'}, - {"delete-all", no_argument, 0, 'D'}, - {"set-all", no_argument, 0, 'S'}, - {NULL, 0, 0, 0} + {"help", no_argument, 0, 'h'}, + {"output", required_argument, 0, 'o'}, + {"in-place", optional_argument, 0, 'i'}, + {"overwrite", no_argument, 0, 'y'}, + {"delete", required_argument, 0, 'd'}, + {"add", required_argument, 0, 'a'}, + {"set", required_argument, 0, 's'}, + {"delete-all", no_argument, 0, 'D'}, + {"set-all", no_argument, 0, 'S'}, + {NULL, 0, 0, 0} }; /** - * Parse the command-line arguments. - * - * Return EXIT_SUCCESS on success, meaning the parsing succeeded and the program execution may - * continue. On error, a relevant message is printed on stderr a non-zero exit code is returned. + * Process the command-line arguments. * * This function does not perform I/O related validations, but checks the consistency of its * arguments. + * + * It returns one of : + * - #ot::status::ok, meaning the process may continue normally. + * - #ot::status::exit_now, meaning there is nothing to do and process should exit successfully. + * This happens when all the user wants is see the help or usage. + * - #ot::status::bad_arguments, meaning the arguments were invalid and the process should exit with + * an error. + * + * Help messages are written on standard output, and error messages on standard error. */ -int ot::parse_options(int argc, char** argv, ot::options& opt) +ot::status ot::process_options(int argc, char** argv, ot::options& opt) { + if (argc == 1) { + fputs(version, stdout); + fputs(usage, stdout); + return status::exit_now; + } int c; while ((c = getopt_long(argc, argv, "ho:i::yd:a:s:DS", getopt_options, NULL)) != -1) { switch (c) { @@ -38,14 +71,14 @@ int ot::parse_options(int argc, char** argv, ot::options& opt) opt.path_out = optarg; if (opt.path_out.empty()) { fputs("output's file path cannot be empty\n", stderr); - return EXIT_FAILURE; + return status::bad_arguments; } break; case 'i': opt.inplace = optarg == nullptr ? ".otmp" : optarg; if (strcmp(opt.inplace, "") == 0) { fputs("the in-place suffix cannot be empty\n", stderr); - return EXIT_FAILURE; + return status::bad_arguments; } break; case 'y': @@ -54,7 +87,7 @@ int ot::parse_options(int argc, char** argv, ot::options& opt) case 'd': if (strchr(optarg, '=') != nullptr) { fprintf(stderr, "invalid field name: '%s'\n", optarg); - return EXIT_FAILURE; + return status::bad_arguments; } opt.to_delete.emplace_back(optarg); break; @@ -62,7 +95,7 @@ int ot::parse_options(int argc, char** argv, ot::options& opt) case 's': if (strchr(optarg, '=') == NULL) { fprintf(stderr, "invalid comment: '%s'\n", optarg); - return EXIT_FAILURE; + return status::bad_arguments; } opt.to_add.emplace_back(optarg); if (c == 's') @@ -76,10 +109,26 @@ int ot::parse_options(int argc, char** argv, ot::options& opt) break; default: /* getopt printed a message */ - return EXIT_FAILURE; + return status::bad_arguments; } } - return EXIT_SUCCESS; + if (opt.print_help) { + puts(version); + puts(usage); + puts(help); + puts("See the man page for extensive documentation."); + return status::exit_now; + } + if (optind != argc - 1) { + fputs("invalid arguments\n", stderr); + return status::bad_arguments; + } + opt.path_in = argv[optind]; + if (opt.path_in.empty()) { + fputs("input's file path cannot be empty\n", stderr); + return status::bad_arguments; + } + return status::ok; } /** diff --git a/src/opustags.cc b/src/opustags.cc index 5e706be..a908ccd 100644 --- a/src/opustags.cc +++ b/src/opustags.cc @@ -10,25 +10,6 @@ #include #include -const char *version = PROJECT_NAME " version " PROJECT_VERSION "\n"; - -const char *usage = - "Usage: opustags --help\n" - " opustags [OPTIONS] FILE\n" - " opustags OPTIONS FILE -o FILE\n"; - -const char *help = - "Options:\n" - " -h, --help print this help\n" - " -o, --output write the modified tags to a file\n" - " -i, --in-place [SUFFIX] use a temporary file then replace the original file\n" - " -y, --overwrite overwrite the output file if it already exists\n" - " -d, --delete FIELD delete all the fields of a specified type\n" - " -a, --add FIELD=VALUE add a field\n" - " -s, --set FIELD=VALUE delete then add a field\n" - " -D, --delete-all delete all the fields!\n" - " -S, --set-all read the fields from stdin\n"; - /** * Display the tags on stdout. * @@ -46,36 +27,12 @@ static void print_tags(ot::opus_tags &tags) } } -int main(int argc, char **argv){ - if(argc == 1){ - fputs(version, stdout); - fputs(usage, stdout); - return EXIT_SUCCESS; - } - ot::options opt; - int exit_code = parse_options(argc, argv, opt); - if (exit_code != EXIT_SUCCESS) - return exit_code; - if (opt.print_help) { - puts(version); - puts(usage); - puts(help); - puts("See the man page for extensive documentation."); - return EXIT_SUCCESS; - } - if(optind != argc - 1){ - fputs("invalid arguments\n", stderr); - return EXIT_FAILURE; - } +static int run(ot::options& opt) +{ if (opt.inplace != nullptr && !opt.path_out.empty()) { fputs("cannot combine --in-place and --output\n", stderr); return EXIT_FAILURE; } - opt.path_in = argv[optind]; - if (opt.path_in.empty()) { - fputs("input's file path cannot be empty\n", stderr); - return EXIT_FAILURE; - } if (!opt.path_out.empty() && opt.path_in != "-" && opt.path_out != "-") { char canon_in[PATH_MAX+1], canon_out[PATH_MAX+1]; if (realpath(opt.path_in.c_str(), canon_in) && realpath(opt.path_out.c_str(), canon_out)) { @@ -247,3 +204,14 @@ int main(int argc, char **argv){ } return EXIT_SUCCESS; } + +int main(int argc, char** argv) { + ot::status rc; + ot::options opt; + rc = process_options(argc, argv, opt); + if (rc == ot::status::exit_now) + return EXIT_SUCCESS; + else if (rc != ot::status::ok) + return EXIT_FAILURE; + return run(opt); +} diff --git a/src/opustags.h b/src/opustags.h index 74b9c1e..7edca25 100644 --- a/src/opustags.h +++ b/src/opustags.h @@ -24,6 +24,8 @@ namespace ot { */ enum class status { ok, + exit_now, + bad_arguments, int_overflow, /** On standard error, errno will give more details. */ standard_error, @@ -204,7 +206,7 @@ struct options { bool print_help = false; }; -int parse_options(int argc, char** argv, options& opt); +status process_options(int argc, char** argv, options& opt); std::list read_tags(FILE* file); /** \} */