Skip to content

Commit

Permalink
Add matching and finding of files with no tags. Closes #9
Browse files Browse the repository at this point in the history
  • Loading branch information
jdberry committed May 2, 2015
1 parent cb9c22c commit b40cc86
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 26 deletions.
25 changes: 17 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,27 @@ The *match* operation prints the file names that match the specified tags. Matc

You can use a wildcard (*) character in the tags list to match against any/all tags. Note, however, that you'll need to quote that * against shell expansion. To display all files in the current directory that have any combination of tags (but not _no_ tags), use:

tag --match \* *
tag --match '*' *

Conversely, to match against paths that have _no_ tags, use an empty tag expression:

tag --match '' *

Turn on --tags display mode for this operation to additionally show the tags on the file:

tag --match \* --tags *
tag --match '*' --tags *

Turn on garrulous output to format those tags onto multiple lines:

tag --match \* --tags --garrulous *
tag --match '*' --tags --garrulous *

You may use short options as well. The following is equivalent to the previous command:

tag -tgm \* *
tag -tgm '*' *

You may use the --enter or --descend options to display the contents of, or recursively descend through, any directories provided. This is similar to the --find operation, but operates recursively. There may be differences in performance and/or output ordering in particular cases:

tag --match \* --descend .
tag --match '*' --descend .

If no file arguments are given, *match* will enumerate and match against the contents of the current directory:

Expand Down Expand Up @@ -128,11 +132,15 @@ The *find* operation searches across your filesystem for all files that contain

You can use the wildcard here too to find all files that contain a tag of any name:

tag --find \*
tag --find '*'

Use an empty tag expression here too to find all files that have _no_ tag:

tag --find ''

And of course you could turn on display of tag names, and even ask it to be garrulous, which displays all files on your system with tags, listing the tags independently on lines below the file names.

tag -tgf \*
tag -tgf '*'

*find* by default will search everywhere that it can. You may supply options to specify a search scope of the user home directory, local disks, or to include attached network file systems.

Expand Down Expand Up @@ -184,6 +192,7 @@ Advanced Usage
----
* Wherever a "tagname" is expected, a list of tags may be provided. They must be comma-separated.
* Tagnames may include spaces, but the entire tag list must be provided as one parameter: "tag1,a multiword tag name,tag3".
* For *match* and *find*, a tag name of '*' is the wildcard and will match any tag. An empty tag expression '' will match only files with no tags.
* 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 --descend 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 --descend, then the operation will apply to the directory itself, rather than to its contents.
Expand All @@ -199,6 +208,6 @@ The following features are contemplated for future enhancement:

* A man page
* Regex or glob matching of tags
* The ability to match or find for "<no tags>"
* More complicated boolean matching criteria


36 changes: 18 additions & 18 deletions Tag/Tag.m
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,7 @@
foo,bar OR baz
foo,bar AND NOT biz,baz
* -- Some tag
NOT * -- No tag?
^ -- No tag?
- -- No tag?
"" -- No tag?
<empty expr> -- No tag
support glob patterns?
support queries for both match and find?
Expand All @@ -80,7 +77,7 @@
#import "TagName.h"
#import <getopt.h>

NSString* const version = @"0.8";
NSString* const version = @"0.8.1";

NSString* const kMDItemUserTags = @"kMDItemUserTags";

Expand Down Expand Up @@ -685,7 +682,8 @@ - (void)doRemove
- (void)doMatch
{
BOOL matchAny = [self wildcardInTagSet:self.tags];

BOOL matchNone = [self.tags count] == 0;

// Enumerate the provided URLs or current directory, listing all paths that match the specified tags
// --all, --enter, and --descend apply
[self enumerateURLsWithBlock:^(NSURL *URL) {
Expand All @@ -695,10 +693,14 @@ - (void)doMatch
NSArray* tagArray;
if (![URL getResourceValue:&tagArray forKey:NSURLTagNamesKey error:&error])
[self reportFatalError:error onURL:URL];
NSUInteger tagCount = [tagArray count];

// If the set of existing tags contains all of the required
// tags then emit
if ((matchAny && [tagArray count]) || [self.tags isSubsetOfSet:[self tagSetFromTagArray:tagArray]])
if ( (matchAny && tagCount > 0)
|| (matchNone && tagCount == 0)
|| (!matchNone && [self.tags isSubsetOfSet:[self tagSetFromTagArray:tagArray]])
)
[self emitURL:URL tags:tagArray];
}];
}
Expand All @@ -723,10 +725,6 @@ - (void)doList

- (void)doFind
{
// Don't do a search for no tags
if (![self.tags count])
return;

// Start a metadata search for files containing all of the given tags
[self initiateMetadataSearchForTags:self.tags];

Expand All @@ -739,21 +737,23 @@ - (void)doFind

- (NSPredicate*)formQueryPredicateForTags:(NSSet*)tagSet
{
NSAssert([tagSet count], @"Assumes there are tags to query for");

// Note for future: the following can be used to search for files that have
// no tags: [NSPredicate predicateWithFormat:@"NOT %K LIKE '*'", kMDItemUserTags]

BOOL matchAny = [self wildcardInTagSet:tagSet];
BOOL matchNone = [tagSet count] == 0;

NSPredicate* result;
if ([self wildcardInTagSet:tagSet])
if (matchAny)
{
result = [NSPredicate predicateWithFormat:@"%K LIKE '*'", kMDItemUserTags];
}
else if (matchNone)
{
result = [NSPredicate predicateWithFormat:@"NOT %K LIKE '*'", kMDItemUserTags];
}
else if ([tagSet count] == 1)
{
result = [NSPredicate predicateWithFormat:@"%K ==[c] %@", kMDItemUserTags, ((TagName*)tagSet.anyObject).visibleName];
}
else
else // if tagSet count > 0
{
NSMutableArray* subpredicates = [NSMutableArray new];
for (TagName* tag in tagSet)
Expand Down

0 comments on commit b40cc86

Please sign in to comment.