mirror of
https://github.com/fmang/opustags.git
synced 2025-01-15 20:53:16 +01:00
support --delete NAME=VALUE
This commit is contained in:
parent
e2a1c06005
commit
06fff8cbeb
18
README.md
18
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.
|
||||
|
40
src/cli.cc
40
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<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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
14
t/cli.cc
14
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)
|
||||
|
21
t/opustags.t
21
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user