jdberry / tag

A command line tool to manipulate tags on Mac OS X files, and to query for files with those tags.
MIT License
1.47k stars 94 forks source link

Add invert-find operation mode #78

Open dstadulis opened 3 months ago

dstadulis commented 3 months ago

These changes introduce a new feature to the tag command-line tool: the ability to find files that do not contain any of the specified tags. This is achieved through the addition of two new operation modes:

  1. OperationModeInvertFind: This mode is triggered by the -F or --invert-find option, and it causes the tool to search for files that do not contain any of the specified tags.
  2. A new method called doInvertFind is added to the Tag class to handle this operation mode. This method calls another new method called findFilesWithoutTagWithUsage:NO, which performs the actual file search.

The changes also modify the existing help text and man page to document the new feature.

Here's a summary of the key changes:

Demonstrating these changes enable users to find files that do not contain specific tags:

$ ./tag(invert_find) bin/tag -F red .
./tag/LICENSE
./tag/Makefile
./tag/README.md
./tag/Tag
./tag/Tag/Tag.h
./tag/Tag/Tag.m
./tag/Tag.xcodeproj
./tag/Tag/TagName.h
./tag/Tag/TagName.m
./tag/bin
./tag/Tag/main.m
./tag/bin/tag
./tag/Tag/tag.1

$ # Set red to ./tag/bin/tag and confirm file no longer is returned from -F search
$ ./tag(invert_find) bin/tag -s red bin/tag
$ ./tag(invert_find) bin/tag --invert-find red .
./tag/LICENSE
./tag/Makefile
./tag/README.md
./tag/Tag
./tag/Tag/Tag.h
./tag/Tag/Tag.m
./tag/Tag.xcodeproj
./tag/Tag/TagName.h
./tag/Tag/TagName.m
./tag/bin
./tag/Tag/main.m
./tag/Tag/tag.1

$ # Success! ./tag/bin/tag no longer is returned from -F search (above)
$ ./tag(invert_find) bin/tag -f red .
./tag/bin/tag
jdberry commented 3 months ago

Instead of searching for files and then post-qualifying them to those that don't contain the tags, is is possible to make this work by forming the metadata query to search for those without tags in the first place?

Currently the code does this:

    else // if tagSet count > 0
    {
        NSMutableArray* subpredicates = [NSMutableArray new];
        for (TagName* tag in tagSet)
            [subpredicates addObject:[NSPredicate predicateWithFormat:@"%K ==[c] %@", kMDItemUserTags, tag.visibleName]];
        result = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
    }

Is it possible to make something like this work?

    else // if tagSet count > 0
    {
        NSMutableArray* subpredicates = [NSMutableArray new];
        for (TagName* tag in tagSet)
            if negativeTagSearch {
                [subpredicates addObject:[NSPredicate predicateWithFormat:@"%K !=[c] %@", kMDItemUserTags, tag.visibleName]];
            } else {
                [subpredicates addObject:[NSPredicate predicateWithFormat:@"%K ==[c] %@", kMDItemUserTags, tag.visibleName]];
            }
        result = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
    }