Skip to content

Commit

Permalink
Compare tags in a case-insensitive fashion for compatibility with Fin…
Browse files Browse the repository at this point in the history
…der. Closes #2.

We try to retain tag case for existing tags on a file. But with this fix we won’t add a second tag on a file that differs only in case, and we’ll match for files with tags that differ in only in case from the match expression.
  • Loading branch information
jdberry committed Nov 1, 2013
1 parent 92b3604 commit 50ba047
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 48 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ INSTALL = /usr/bin/install
BINDIR = ${prefix}/bin
MANDIR = ${prefix}/man

SRCS = Tag/main.m Tag/Tag.m
SRCS = Tag/main.m Tag/Tag.m Tag/TagName.m
LIBS = -framework Foundation \
-framework CoreServices

Expand Down
6 changes: 6 additions & 0 deletions Tag.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
901F1415181B16A00007C852 /* tag.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = 901F1414181B16A00007C852 /* tag.1 */; };
901F141D181B25F10007C852 /* Tag.m in Sources */ = {isa = PBXBuildFile; fileRef = 901F141C181B25F10007C852 /* Tag.m */; };
901F141F181B469E0007C852 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 901F141E181B469E0007C852 /* CoreServices.framework */; };
90A6AB6E1823FE7B002AE709 /* TagName.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A6AB6D1823FE7B002AE709 /* TagName.m */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -39,6 +40,8 @@
901F1423181B71AF0007C852 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
901F1425181B90930007C852 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
901F1426181B90930007C852 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; };
90A6AB6C1823FE7B002AE709 /* TagName.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagName.h; sourceTree = "<group>"; };
90A6AB6D1823FE7B002AE709 /* TagName.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TagName.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -90,6 +93,8 @@
901F1414181B16A00007C852 /* tag.1 */,
901F141B181B25F10007C852 /* Tag.h */,
901F141C181B25F10007C852 /* Tag.m */,
90A6AB6C1823FE7B002AE709 /* TagName.h */,
90A6AB6D1823FE7B002AE709 /* TagName.m */,
901F1412181B16A00007C852 /* Supporting Files */,
);
path = Tag;
Expand Down Expand Up @@ -154,6 +159,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
90A6AB6E1823FE7B002AE709 /* TagName.m in Sources */,
901F141D181B25F10007C852 /* Tag.m in Sources */,
901F1411181B16A00007C852 /* main.m in Sources */,
);
Expand Down
2 changes: 1 addition & 1 deletion Tag/Tag.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ typedef NS_ENUM(int, SearchScope) {
@property (assign, nonatomic) OutputFlags outputFlags;
@property (assign, nonatomic) SearchScope searchScope;

@property (copy, nonatomic) NSArray* tags;
@property (copy, nonatomic) NSSet* tags;
@property (copy, nonatomic) NSArray* URLs;

- (void)parseCommandLineArgv:(char * const *)argv argc:(int)argc;
Expand Down
120 changes: 74 additions & 46 deletions Tag/Tag.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@


#import "Tag.h"
#import "TagName.h"
#import <getopt.h>

NSString* const version = @"0.7.2";

NSString* const version = @"0.7.1";
NSString* const kMDItemUserTags = @"kMDItemUserTags";


@interface Tag ()
Expand Down Expand Up @@ -257,10 +259,10 @@ - (void)parseTagsArgument:(NSString*)arg
{
NSString* trimmed = [component stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([trimmed length])
[uniqueTags addObject:trimmed];
[uniqueTags addObject:[[TagName alloc] initWithTag:trimmed]];
}

self.tags = [uniqueTags allObjects];
self.tags = uniqueTags;
}


Expand Down Expand Up @@ -344,12 +346,6 @@ - (void)reportFatalError:(NSError*)error onURL:(NSURL*)URL
}


- (BOOL)wildcardInArray:(NSArray*)array
{
return [array containsObject:@"*"];
}


- (NSString*)string:(NSString*)s paddedToMinimumLength:(int)minLength
{
NSInteger length = [s length];
Expand All @@ -360,16 +356,16 @@ - (NSString*)string:(NSString*)s paddedToMinimumLength:(int)minLength
}


- (void)emitURL:(NSURL*)URL tags:(NSArray*)tags
- (void)emitURL:(NSURL*)URL tags:(NSArray*)tagArray
{
NSString* fileName = (_outputFlags & OutputFlagsName) ? [URL relativePath] : nil;

NSString* tagString = nil;
NSString* tagSeparator;
int minFileFieldWidth = 0;
if ((_outputFlags & OutputFlagsTags) && [tags count])
if ((_outputFlags & OutputFlagsTags) && [tagArray count])
{
NSArray* sortedTags = [tags sortedArrayUsingSelector:@selector(compare:)];
NSArray* sortedTags = [tagArray sortedArrayUsingSelector:@selector(compare:)];
if (_outputFlags & OutputFlagsGarrulous)
{
tagSeparator = fileName ? @"\n " : @"\n"; // Don't indent tags if no filename
Expand Down Expand Up @@ -400,19 +396,52 @@ - (void)emitURL:(NSURL*)URL tags:(NSArray*)tags
}


- (BOOL)wildcardInTagSet:(NSSet*)set
{
static TagName* wildcard;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
wildcard = [[TagName alloc] initWithTag:@"*"];
});
return [set containsObject:wildcard];
}


- (NSMutableSet*)tagSetFromArrayOfTags:(NSArray*)tagArray
{
NSMutableSet* set = [[NSMutableSet alloc] initWithCapacity:[tagArray count]];
for (NSString* tag in tagArray)
[set addObject:[[TagName alloc] initWithTag:tag]];
return set;
}


- (NSArray*)tagArrayFromTagSet:(NSSet*)tagSet
{
NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:[tagSet count]];
for (TagName* tag in tagSet)
[array addObject:tag.visibleName];
return array;
}


- (void)doSet
{
NSArray* tagArray = [self tagArrayFromTagSet:self.tags];
for (NSURL* URL in self.URLs)
{
NSError* error;
if (![URL setResourceValue:self.tags forKey:NSURLTagNamesKey error:&error])
if (![URL setResourceValue:tagArray forKey:NSURLTagNamesKey error:&error])
[self reportFatalError:error onURL:URL];
}
}


- (void)doAdd
{
if (![self.tags count])
return;

for (NSURL* URL in self.URLs)
{
@autoreleasepool {
Expand All @@ -423,12 +452,12 @@ - (void)doAdd
if (![URL getResourceValue:&existingTags forKey:NSURLTagNamesKey error:&error])
[self reportFatalError:error onURL:URL];

// Form the union of the existing tags + new tags
NSMutableSet* tagSet = [[NSMutableSet alloc] initWithArray:existingTags];
[tagSet addObjectsFromArray:self.tags];
// Form the union of the existing tags + new tags.
NSMutableSet* tagSet = [self tagSetFromArrayOfTags:existingTags];
[tagSet unionSet:self.tags];

// Set all the new tags onto the item
if (![URL setResourceValue:[tagSet allObjects] forKey:NSURLTagNamesKey error:&error])
if (![URL setResourceValue:[self tagArrayFromTagSet:tagSet] forKey:NSURLTagNamesKey error:&error])
[self reportFatalError:error onURL:URL];
}
}
Expand All @@ -437,8 +466,10 @@ - (void)doAdd

- (void)doRemove
{
BOOL matchAny = [self wildcardInArray:self.tags];
NSSet* tagsToRemove = [NSSet setWithArray:self.tags];
if (![self.tags count])
return;

BOOL matchAny = [self wildcardInTagSet:self.tags];

for (NSURL* URL in self.URLs)
{
Expand All @@ -451,14 +482,14 @@ - (void)doRemove
[self reportFatalError:error onURL:URL];

// Form a set containing difference of the existing tags - tags to remove
NSMutableSet* tagSet = [[NSMutableSet alloc] initWithArray:existingTags];
NSMutableSet* tagSet = [self tagSetFromArrayOfTags:existingTags];
if (matchAny)
[tagSet removeAllObjects];
else
[tagSet minusSet:tagsToRemove];
[tagSet minusSet:self.tags];

// Set the revised tags onto the item
if (![URL setResourceValue:[tagSet allObjects] forKey:NSURLTagNamesKey error:&error])
if (![URL setResourceValue:[self tagArrayFromTagSet:tagSet] forKey:NSURLTagNamesKey error:&error])
[self reportFatalError:error onURL:URL];
}
}
Expand All @@ -467,8 +498,7 @@ - (void)doRemove

- (void)doMatch
{
BOOL matchAny = [self wildcardInArray:self.tags];
NSSet* requiredTags = [NSSet setWithArray:self.tags];
BOOL matchAny = [self wildcardInTagSet:self.tags];

// Display only those items containing all the tags listed
for (NSURL* URL in self.URLs)
Expand All @@ -477,14 +507,14 @@ - (void)doMatch
NSError* error;

// Get the tags on the URL
NSArray* tags;
if (![URL getResourceValue:&tags forKey:NSURLTagNamesKey error:&error])
NSArray* tagArray;
if (![URL getResourceValue:&tagArray forKey:NSURLTagNamesKey error:&error])
[self reportFatalError:error onURL:URL];

// If the set of existing tags contains all of the required
// tags then print the path
if ((matchAny && [tags count]) || [requiredTags isSubsetOfSet:[NSSet setWithArray:tags]])
[self emitURL:URL tags:tags];
if ((matchAny && [tagArray count]) || [self.tags isSubsetOfSet:[self tagSetFromArrayOfTags:tagArray]])
[self emitURL:URL tags:tagArray];
}
}
}
Expand All @@ -497,11 +527,11 @@ - (void)doList
{
@autoreleasepool {
NSError* error;
NSArray* tags;
if (![URL getResourceValue:&tags forKey:NSURLTagNamesKey error:&error])
NSArray* tagArray;
if (![URL getResourceValue:&tagArray forKey:NSURLTagNamesKey error:&error])
[self reportFatalError:error onURL:URL];

[self emitURL:URL tags:tags];
[self emitURL:URL tags:tagArray];
}
}
}
Expand All @@ -523,25 +553,24 @@ - (void)doFind
}


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

NSPredicate* result;
if ([self wildcardInArray:tags])
if ([self wildcardInTagSet:tagSet])
{
result = [NSPredicate predicateWithFormat:@"kMDItemUserTags LIKE '*'"];
result = [NSPredicate predicateWithFormat:@"%K LIKE '*'", kMDItemUserTags];
}
else if ([tags count] == 1)
else if ([tagSet count] == 1)
{
result = [NSPredicate predicateWithFormat:@"kMDItemUserTags == %@", tags[0]];
result = [NSPredicate predicateWithFormat:@"%K ==[c] %@", kMDItemUserTags, ((TagName*)tagSet.anyObject).visibleName];
}
else
{
NSMutableArray* subpredicates = [NSMutableArray new];
for (NSString* tag in tags)
[subpredicates addObject:[NSPredicate predicateWithFormat:@"kMDItemUserTags == %@", tag]];

for (TagName* tag in tagSet)
[subpredicates addObject:[NSPredicate predicateWithFormat:@"%K ==[c] %@", kMDItemUserTags, tag.visibleName]];
result = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
}

Expand All @@ -568,7 +597,7 @@ - (NSArray*)searchScopesFromSearchScope:(SearchScope)scope
}


- (void)initiateMetadataSearchForTags:(NSArray*)tags
- (void)initiateMetadataSearchForTags:(NSSet*)tagSet
{
// Create the metadata query instance
self.metadataQuery=[[NSMetadataQuery alloc] init];
Expand All @@ -585,15 +614,15 @@ - (void)initiateMetadataSearchForTags:(NSArray*)tags
object:_metadataQuery];

// Configure the search predicate
NSPredicate *searchPredicate = [self formQueryPredicateForTags:tags];
NSPredicate *searchPredicate = [self formQueryPredicateForTags:tagSet];
[_metadataQuery setPredicate:searchPredicate];

// Set the search scope
NSArray *searchScopes = [self searchScopesFromSearchScope:self.searchScope];
[_metadataQuery setSearchScopes:searchScopes];

// Configure the sorting of the results
// (note that the query can't sort by the item path, which likely makes this useless)
// (note that the query can't sort by the item path, which makes sorting less usefull)
NSSortDescriptor *sortKeys = [[NSSortDescriptor alloc] initWithKey:(id)kMDItemDisplayName
ascending:YES];
[_metadataQuery setSortDescriptors:[NSArray arrayWithObject:sortKeys]];
Expand Down Expand Up @@ -624,11 +653,10 @@ - (void)queryComplete:sender;
@autoreleasepool {
NSMetadataItem* theResult = [_metadataQuery resultAtIndex:i];

// kMDItemPath, kMDItemDisplayName
NSURL* URL = [NSURL fileURLWithPath:[theResult valueForAttribute:(NSString *)kMDItemPath]];
NSArray* tags = [theResult valueForAttribute:@"kMDItemUserTags"];
NSArray* tagArray = [theResult valueForAttribute:kMDItemUserTags];

[self emitURL:URL tags:tags];
[self emitURL:URL tags:tagArray];
}
}

Expand Down
44 changes: 44 additions & 0 deletions Tag/TagName.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// TagName.h
// Tag
//
// Created by James Berry on 11/1/13.
//
// The MIT License (MIT)
//
// Copyright (c) 2013 James Berry
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

#import <Foundation/Foundation.h>

// Items of this class are compared for equality in a case-insensitive fashion,
// but retain their original case in visibleName. TagName objects are used
// in forming sets of tags.

@interface TagName : NSObject

- (instancetype)initWithTag:(NSString*)tag;

@property (readonly) NSString* visibleName;
@property (readonly) NSString* comparableName;

- (BOOL)isEqualToTagName:(TagName*)tagName;

@end
Loading

0 comments on commit 50ba047

Please sign in to comment.