support --delete NAME=VALUE

This commit is contained in:
Frédéric Mangano-Tarumi 2018-12-17 20:00:27 -05:00
parent e2a1c06005
commit 06fff8cbeb
5 changed files with 62 additions and 47 deletions

View File

@ -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.

View File

@ -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<std::string>& comments)
return ot::st::ok;
}
void ot::delete_comments(std::list<std::string>& comments, const std::string& field_name)
void ot::delete_comments(std::list<std::string>& 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);
}
}

View File

@ -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<std::string> to_delete;
@ -460,11 +459,12 @@ void print_comments(const std::list<std::string>& comments, FILE* output);
status read_comments(FILE* input, std::list<std::string>& 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<std::string>& comments, const std::string& field_name);
void delete_comments(std::list<std::string>& comments, const std::string& selector);
/**
* Main entry point to the opustags program, and pretty much the same as calling opustags from the

View File

@ -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)

View File

@ -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