From 06fff8cbeb77b80e9bc272cbf89615fd60b233a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Mangano-Tarumi?= Date: Mon, 17 Dec 2018 20:00:27 -0500 Subject: [PATCH] support --delete NAME=VALUE --- README.md | 18 +++++++++--------- src/cli.cc | 40 ++++++++++++++++++++++++---------------- src/opustags.h | 16 ++++++++-------- t/cli.cc | 14 ++++++++++---- t/opustags.t | 21 +++++++++++---------- 5 files changed, 62 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 732398d..f54610a 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,14 @@ Documentation opustags OPTIONS FILE -o FILE Options: - -h, --help print this help - -o, --output FILE set the output file - -i, --in-place overwrite the input file instead of writing a different output file - -y, --overwrite overwrite the output file if it already exists - -a, --add FIELD=VALUE add a comment - -d, --delete FIELD delete all previously existing comments of a specific type - -D, --delete-all delete all the previously existing comments - -s, --set FIELD=VALUE replace a comment (shorthand for --delete FIELD --add FIELD=VALUE) - -S, --set-all replace all the comments with the ones read from standard input + -h, --help print this help + -o, --output FILE specify the output file + -i, --in-place overwrite the input file + -y, --overwrite overwrite the output file if it already exists + -a, --add FIELD=VALUE add a comment + -d, --delete FIELD[=VALUE] delete previously existing comments + -D, --delete-all delete all the previously existing comments + -s, --set FIELD=VALUE replace a comment + -S, --set-all import comments from standard input See the man page, `opustags.1`, for extensive documentation. diff --git a/src/cli.cc b/src/cli.cc index c1663c6..4583427 100644 --- a/src/cli.cc +++ b/src/cli.cc @@ -28,15 +28,15 @@ Usage: opustags --help opustags OPTIONS FILE -o FILE Options: - -h, --help print this help - -o, --output FILE set the output file - -i, --in-place overwrite the input file instead of writing a different output file - -y, --overwrite overwrite the output file if it already exists - -a, --add FIELD=VALUE add a comment - -d, --delete FIELD delete all previously existing comments of a specific type - -D, --delete-all delete all the previously existing comments - -s, --set FIELD=VALUE replace a comment (shorthand for --delete FIELD --add FIELD=VALUE) - -S, --set-all replace all the comments with the ones read from standard input + -h, --help print this help + -o, --output FILE specify the output file + -i, --in-place overwrite the input file + -y, --overwrite overwrite the output file if it already exists + -a, --add FIELD=VALUE add a comment + -d, --delete FIELD[=VALUE] delete previously existing comments + -D, --delete-all delete all the previously existing comments + -s, --set FIELD=VALUE replace a comment + -S, --set-all import comments from standard input See the man page for extensive documentation. )raw"; @@ -88,8 +88,6 @@ ot::status ot::parse_options(int argc, char** argv, ot::options& opt) rc = to_utf8(optarg, strlen(optarg), utf8); if (rc != ot::st::ok) return {st::bad_arguments, "Could not encode argument into UTF-8: " + rc.message}; - if (strchr(utf8.c_str(), '=') != nullptr) - return {st::bad_arguments, "Invalid field name '"s + optarg + "'."}; opt.to_delete.emplace_back(std::move(utf8)); break; case 'a': @@ -205,15 +203,25 @@ ot::status ot::read_comments(FILE* input, std::list& comments) return ot::st::ok; } -void ot::delete_comments(std::list& comments, const std::string& field_name) +void ot::delete_comments(std::list& comments, const std::string& selector) { - auto field_len = field_name.size(); + auto name = selector.data(); + auto equal = selector.find('='); + auto value = (equal == std::string::npos ? nullptr : name + equal + 1); + auto name_len = value ? equal : selector.size(); + auto value_len = value ? selector.size() - equal - 1 : 0; auto it = comments.begin(), end = comments.end(); while (it != end) { auto current = it++; - if (current->size() > field_len + 1 && - (*current)[field_len] == '=' && - strncasecmp(current->data(), field_name.data(), field_len) == 0) + bool name_match = current->size() > name_len + 1 && + (*current)[name_len] == '=' && + strncasecmp(current->data(), name, name_len) == 0; + if (!name_match) + continue; + bool value_match = value == nullptr || + (current->size() == selector.size() && + memcmp(current->data() + equal + 1, value, value_len) == 0); + if (value_match) comments.erase(current); } } diff --git a/src/opustags.h b/src/opustags.h index 7b1e659..a747d8b 100644 --- a/src/opustags.h +++ b/src/opustags.h @@ -394,17 +394,16 @@ struct options { */ bool overwrite = false; /** - * List of field names to delete. `{"ARTIST"}` will delete *all* the comments `ARTIST=*`. It - * is currently case-sensitive. When #delete_all is true, it becomes meaningless. + * List of comments to delete. Each string is a selector according to the definition of + * #delete_comments. + * + * When #delete_all is true, this option is meaningless. * * #to_add takes precedence over #to_delete, so if the same comment appears in both lists, * the one in #to_delete applies only to the previously existing tags. * * The strings are stored in UTF-8. * - * \todo Consider making it case-insensitive. - * \todo Allow values like `ARTIST=x` to delete only the ARTIST comment whose value is x. - * * Option: --delete, --set */ std::vector to_delete; @@ -460,11 +459,12 @@ void print_comments(const std::list& comments, FILE* output); status read_comments(FILE* input, std::list& comments); /** - * Remove all the comments whose field name is equal to the special one, case-sensitive. + * Remove all comments matching the specified selector, which may either be a field name or a + * NAME=VALUE pair. The field name is case-insensitive. * - * \todo Accept fields like X=Y to remove only comments X=Y, instead of all X. + * The strings are all UTF-8. */ -void delete_comments(std::list& comments, const std::string& field_name); +void delete_comments(std::list& comments, const std::string& selector); /** * Main entry point to the opustags program, and pretty much the same as calling opustags from the diff --git a/t/cli.cc b/t/cli.cc index fce7216..a0877c1 100644 --- a/t/cli.cc +++ b/t/cli.cc @@ -50,9 +50,9 @@ void check_good_arguments() if (!opt.print_help) throw failure("did not catch --help"); - opt = parse({"opustags", "x", "--output", "y", "-D", "-s", "X=Y Z"}); + opt = parse({"opustags", "x", "--output", "y", "-D", "-s", "X=Y Z", "-d", "a=b"}); if (opt.path_in != "x" || opt.path_out != "y" || !opt.delete_all || opt.overwrite || - opt.to_delete.size() != 1 || opt.to_delete[0] != "X" || + opt.to_delete.size() != 2 || opt.to_delete[0] != "X" || opt.to_delete[1] != "a=b" || opt.to_add.size() != 1 || opt.to_add[0] != "X=Y Z") throw failure("unexpected option parsing result for case #1"); @@ -74,7 +74,6 @@ void check_bad_arguments() }; error_case({"opustags"}, "No arguments specified. Use -h for help.", "no arguments"); error_case({"opustags", "--output", ""}, "Output file path cannot be empty.", "empty output path"); - error_case({"opustags", "--delete", "X="}, "Invalid field name 'X='.", "bad field name for -d"); error_case({"opustags", "-a", "X"}, "Comment does not contain an equal sign: X.", "bad comment for -a"); error_case({"opustags", "--set", "X"}, "Comment does not contain an equal sign: X.", "bad comment for --set"); error_case({"opustags", "-a"}, "Missing value for option '-a'.", "short option with missing value"); @@ -106,7 +105,14 @@ static void check_delete_comments() ot::delete_comments(edited, "Title"); C expected = {"ARTIST=A", "artIst=B"}; if (!std::equal(edited.begin(), edited.end(), expected.begin(), expected.end())) - throw failure("did not delete Title correctly"); + throw failure("did not delete all titles correctly"); + + edited = original; + ot::delete_comments(edited, "titlE=Y"); + ot::delete_comments(edited, "Title=z"); + expected = {"TITLE=X", "Title=Z", "ARTIST=A", "artIst=B"}; + if (!std::equal(edited.begin(), edited.end(), expected.begin(), expected.end())) + throw failure("did not delete a specific title correctly"); } int main(int argc, char **argv) diff --git a/t/opustags.t b/t/opustags.t index c8fe613..06b653a 100755 --- a/t/opustags.t +++ b/t/opustags.t @@ -57,15 +57,15 @@ Usage: opustags --help opustags OPTIONS FILE -o FILE Options: - -h, --help print this help - -o, --output FILE set the output file - -i, --in-place overwrite the input file instead of writing a different output file - -y, --overwrite overwrite the output file if it already exists - -a, --add FIELD=VALUE add a comment - -d, --delete FIELD delete all previously existing comments of a specific type - -D, --delete-all delete all the previously existing comments - -s, --set FIELD=VALUE replace a comment (shorthand for --delete FIELD --add FIELD=VALUE) - -S, --set-all replace all the comments with the ones read from standard input + -h, --help print this help + -o, --output FILE specify the output file + -i, --in-place overwrite the input file + -y, --overwrite overwrite the output file if it already exists + -a, --add FIELD=VALUE add a comment + -d, --delete FIELD[=VALUE] delete previously existing comments + -D, --delete-all delete all the previously existing comments + -s, --set FIELD=VALUE replace a comment + -S, --set-all import comments from standard input See the man page for extensive documentation. EOF @@ -130,7 +130,8 @@ X=2 X=3 EOF -is_deeply(opustags(qw(out.opus -d A -d foo -s X=4 -a TITLE=gobble -d TITLE)), [<<'EOF', '', 0], 'dry editing'); +is_deeply(opustags(qw(out.opus -d A -d foo -s X=4 -a TITLE=gobble -d title=七面鳥)), [<<'EOF', '', 0], 'dry editing'); +TITLE=Foo Bar encoder=whatever 1=2 X=4