From f4079488f223f33fd8fa741e4e9e89dbf0ac060e Mon Sep 17 00:00:00 2001 From: sancoder Date: Fri, 15 Nov 2019 20:13:52 -0800 Subject: [PATCH] Added invert mode --- README.md | 10 +++++++++- Tag/Tag.h | 1 + Tag/Tag.m | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 90707c1..e58b32d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Usage tag -a | --add ... Add tags to file tag -r | --remove ... Remove tags from file tag -s | --set ... Set tags on file + tag -i | --invert ... Invert tags on file tag -m | --match ... Display files with matching tags tag -f | --find ... Find all files with tags, limited to paths if present tag -l | --list ... List the tags on file @@ -61,6 +62,13 @@ The *set* operation replaces all tags on the specified files with one or more ne tag --set tagname file tag --set tagname1,tagname2,... file1 file2... +### Invert tags on a file + +The *invert* operation sets tags which are not present and remove tags which are present on the specified files. + + tag --invert tagname file + tag --invert tagname1,tagname2,... file1 file2... + ### Show files matching tags The *match* operation prints the file names that match the specified tags. Matched files must have at least *all* of the tags specified. Note that *match* matches only against the files that are provided as parameters (and those that it encounters if you use the --enter or --recursive options). To search for tagged files across your filesystem, see the *find* operation. @@ -223,7 +231,7 @@ Advanced Usage * Wherever a "file" is expected, a list of files may be used instead. These are provided as separate parameters. * Note that directories can be tagged as well, so directories may be specified instead of files. * The --all, --enter, and --recursive options apply to --add, --remove, --set, --match, and --list, and control whether hidden files are processed and whether directories are entered and/or processed recursively. If a directory is supplied, but neither of --enter or --recursive, then the operation will apply to the directory itself, rather than to its contents. -* The operation selector --add, --remove, --set, --match, --list, --find, or --usage may be abbreviated as -a, -r, -s, -m, -l, -f, or -u respectively. All of the options have a short version, in fact. See see the synopsis above, or output from help. +* The operation selector --add, --remove, --set, --invert, --match, --list, --find, or --usage may be abbreviated as -a, -r, -s, -i, -m, -l, -f, or -u respectively. All of the options have a short version, in fact. See see the synopsis above, or output from help. * If no operation selector is given, the operation will default to *list*. * A *list* operation will default to the current directory if no directory is given. * For compatibility with Finder, tags are compared in a case-insensitive manner. diff --git a/Tag/Tag.h b/Tag/Tag.h index 05227c9..fc6ce0a 100644 --- a/Tag/Tag.h +++ b/Tag/Tag.h @@ -34,6 +34,7 @@ typedef NS_ENUM(int, OperationMode) { OperationModeSet = 's', OperationModeAdd = 'a', OperationModeRemove = 'r', + OperationModeInvert = 'i', OperationModeMatch = 'm', OperationModeFind = 'f', OperationModeUsage = 'u', diff --git a/Tag/Tag.m b/Tag/Tag.m index 686e630..7c07e7c 100644 --- a/Tag/Tag.m +++ b/Tag/Tag.m @@ -122,6 +122,7 @@ - (void)parseCommandLineArgv:(char * const *)argv argc:(int)argc { "set", required_argument, 0, OperationModeSet }, { "add", required_argument, 0, OperationModeAdd }, { "remove", required_argument, 0, OperationModeRemove }, + { "invert", required_argument, 0, OperationModeInvert }, { "match", required_argument, 0, OperationModeMatch }, { "find", required_argument, 0, OperationModeFind }, { "usage", optional_argument, 0, OperationModeUsage }, @@ -182,13 +183,14 @@ - (void)parseCommandLineArgv:(char * const *)argv argc:(int)argc // Parse Options int option_char; int option_index; - while ((option_char = getopt_long(argc, argv, "s:a:r:m:f:u::lAeRdnNtTgGcp0hv", options, &option_index)) != -1) + while ((option_char = getopt_long(argc, argv, "s:a:r:i:m:f:u::lAeRdnNtTgGcp0hv", options, &option_index)) != -1) { switch (option_char) { case OperationModeSet: case OperationModeAdd: case OperationModeRemove: + case OperationModeInvert: case OperationModeMatch: case OperationModeFind: case OperationModeUsage: @@ -369,6 +371,7 @@ - (void)displayHelp " tag -a | --add ... Add tags to file\n" " tag -r | --remove ... Remove tags from file\n" " tag -s | --set ... Set tags on file\n" + " tag -i | --invert ... Invert tags on file\n" " tag -m | --match ... Display files with matching tags\n" " tag -f | --find ... Find all files with tags (-A, -e, -R ignored)\n" " tag -u | --usage ... Display tags used, with usage counts\n" @@ -495,6 +498,10 @@ - (void)performOperation [self doRemove]; break; + case OperationModeInvert: + [self doInvert]; + break; + case OperationModeMatch: [self doMatch]; break; @@ -806,6 +813,49 @@ - (void)doRemove } +- (void)doInvert +{ + // If there are no tags to invert, we're done + if (![self.tags count]) + return; + + // Only perform invert on specified URLs + // (we don't implicitly enumerate the current directory) + if ([self.URLs count] == 0) + return; + + // Enumerate the provided URLs, adding tags to each + // --all, --enter, and --recursive apply + [self enumerateURLsWithBlock:^(NSURL *URL) { + NSError* error; + + // Get the existing tags + NSArray* existingTags; + if (![URL getResourceValue:&existingTags forKey:NSURLTagNamesKey error:&error]) + [self reportFatalError:error onURL:URL]; + + NSMutableSet* curTags = [self tagSetFromTagArray:existingTags]; + + // Calculate tags to be removed + NSMutableSet* tagsToRemove = [curTags mutableCopy]; + [tagsToRemove intersectSet:self.tags]; + + // Calculate tags to be added + NSMutableSet* tagsToAdd = [self.tags mutableCopy]; + [tagsToAdd minusSet:tagsToRemove]; + + // Remove tags to be removed + [curTags minusSet:tagsToRemove]; + // Add tags to be added + [curTags unionSet:tagsToAdd]; + + // Set all the new tags onto the item + if (![URL setResourceValue:[self tagArrayFromTagSet:curTags] forKey:NSURLTagNamesKey error:&error]) + [self reportFatalError:error onURL:URL]; + }]; +} + + - (void)doMatch { BOOL matchAny = [self wildcardInTagSet:self.tags];