├── dnc.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── thall.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── README.md └── dnc └── main.swift /dnc.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dnc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /dnc.xcodeproj/xcuserdata/thall.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | dnc.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dnc - Does Not Contain 2 | 3 | I occassioanlly need to scan a folder and all of its subdirectories to see if any of them DO NOT contain files of a certain type. 4 | 5 | I'm fully aware you can do this with some combination of shell commands, but I always spent 20 minutes googling for how to do it again every time I needed to. It was faster just to write this small utility myself. 6 | 7 | USAGE: dnc --directory --query [--invert] 8 | 9 | OPTIONS: 10 | -d, --directory 11 | The directory to recursively scan. 12 | -q, --query A string to search for in the filename of the directory's files. 13 | --invert Invert the query. i.e., show directories that DO contain the query. 14 | -h, --help Show help information. 15 | 16 | My primary use-case is finding which albums in my music collection (folders on disk) are in a lossy format. This gives me a quick shopping list of used CDs to buy when I need something mindless to do during quarantine. 17 | 18 | A typical command would be 19 | 20 | dnc -d /path/to/music -q flac 21 | 22 | That will output something along the lines of 23 | 24 | /path/to/music/Violent Femmes/1999-11-23 - Viva Wisconsin 25 | /path/to/music/Weezer/1994-05-10 - Weezer 26 | /path/to/music/White Denim/2013-10-29 - Corsicana Lemonade 27 | /path/to/music/Yonder Mountain String Band/A Decade of Yonder Live, Vol 9_ 6_29_2006 Apple Valley, MN 28 | /path/to/music/Zero 7/The Garden 29 | 30 | which means of all the folders recursively inside `/path/to/music`, those directories DO NOT contain any `flac` files. 31 | 32 | A key difference of this script than many of the solutions that Google gives me, is I'm _not_ interested in every _file_ that does not match `query`. I just want to know about any _folders_ that do not contain matching files. 33 | 34 | You can use the full command line arguments listed above with `--help`, or you can quickly run the command on your current directory by only passing a query string like this: 35 | 36 | dnc flac 37 | 38 | 39 | ## Download 40 | 41 | The latest build is available to download on the [Releases](https://github.com/tylerhall/dnc/releases) page. The builds aren't notarized. So be sure to ask Apple if it's OK to run this on your Mac. 42 | -------------------------------------------------------------------------------- /dnc/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // dnc 4 | // 5 | // Created by Tyler Hall on 10/10/20. 6 | // 7 | 8 | import Foundation 9 | import ArgumentParser 10 | 11 | struct DNCOptions: ParsableArguments { 12 | @Option(name: .shortAndLong, help: ArgumentHelp("The directory to recursively scan.", valueName: "directory")) 13 | var directory: String 14 | 15 | @Option(name: .shortAndLong, help: ArgumentHelp("A string to search for in the filename of the directory's files.", valueName: "string")) 16 | var query: String 17 | 18 | @Flag(help: "Invert the query. i.e., show directories that DO contain the query.") 19 | var invert = false 20 | } 21 | 22 | var directoryPath: String 23 | var query: String 24 | var invert = false 25 | 26 | if (CommandLine.arguments.count == 2) && (CommandLine.arguments[1] != "--help") { 27 | directoryPath = FileManager.default.currentDirectoryPath 28 | query = CommandLine.arguments[1] 29 | } else { 30 | let options = DNCOptions.parseOrExit() 31 | directoryPath = options.directory 32 | query = options.query 33 | invert = options.invert 34 | } 35 | 36 | var isDir: ObjCBool = false 37 | guard FileManager.default.fileExists(atPath: directoryPath, isDirectory: &isDir), isDir.boolValue else { 38 | print("Error: \(directoryPath) is not accessible.") 39 | exit(EXIT_FAILURE) 40 | } 41 | 42 | func checkDirectoryURL(_ dirURL: URL, query: String) { 43 | guard let files = try? FileManager.default.contentsOfDirectory(at: dirURL, includingPropertiesForKeys: nil, options: []) else { return } 44 | 45 | var isMissingQuery = true 46 | for fileURL in files { 47 | if fileURL.lastPathComponent.contains(query) { 48 | isMissingQuery = false 49 | } 50 | 51 | var isDir: ObjCBool = false 52 | let exists = FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDir) 53 | if exists && isDir.boolValue { 54 | checkDirectoryURL(fileURL, query: query) 55 | } 56 | } 57 | 58 | if invert { 59 | if !isMissingQuery { 60 | print(dirURL.path) 61 | } 62 | } else { 63 | if isMissingQuery { 64 | print(dirURL.path) 65 | } 66 | } 67 | } 68 | 69 | let directoryURL = URL(fileURLWithPath: directoryPath) 70 | checkDirectoryURL(directoryURL, query: query) 71 | -------------------------------------------------------------------------------- /dnc.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C65583CA2532085C00A440FD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C65583C92532085C00A440FD /* main.swift */; }; 11 | C65583D32532087B00A440FD /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C65583D22532087B00A440FD /* ArgumentParser */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | C65583C42532085C00A440FD /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = /usr/share/man/man1/; 19 | dstSubfolderSpec = 0; 20 | files = ( 21 | ); 22 | runOnlyForDeploymentPostprocessing = 1; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | C65583C62532085C00A440FD /* dnc */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dnc; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | C65583C92532085C00A440FD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | C65583C32532085C00A440FD /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | C65583D32532087B00A440FD /* ArgumentParser in Frameworks */, 37 | ); 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXFrameworksBuildPhase section */ 41 | 42 | /* Begin PBXGroup section */ 43 | C65583BD2532085C00A440FD = { 44 | isa = PBXGroup; 45 | children = ( 46 | C65583C82532085C00A440FD /* dnc */, 47 | C65583C72532085C00A440FD /* Products */, 48 | ); 49 | sourceTree = ""; 50 | }; 51 | C65583C72532085C00A440FD /* Products */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | C65583C62532085C00A440FD /* dnc */, 55 | ); 56 | name = Products; 57 | sourceTree = ""; 58 | }; 59 | C65583C82532085C00A440FD /* dnc */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | C65583C92532085C00A440FD /* main.swift */, 63 | ); 64 | path = dnc; 65 | sourceTree = ""; 66 | }; 67 | /* End PBXGroup section */ 68 | 69 | /* Begin PBXNativeTarget section */ 70 | C65583C52532085C00A440FD /* dnc */ = { 71 | isa = PBXNativeTarget; 72 | buildConfigurationList = C65583CD2532085C00A440FD /* Build configuration list for PBXNativeTarget "dnc" */; 73 | buildPhases = ( 74 | C65583C22532085C00A440FD /* Sources */, 75 | C65583C32532085C00A440FD /* Frameworks */, 76 | C65583C42532085C00A440FD /* CopyFiles */, 77 | ); 78 | buildRules = ( 79 | ); 80 | dependencies = ( 81 | ); 82 | name = dnc; 83 | packageProductDependencies = ( 84 | C65583D22532087B00A440FD /* ArgumentParser */, 85 | ); 86 | productName = dnc; 87 | productReference = C65583C62532085C00A440FD /* dnc */; 88 | productType = "com.apple.product-type.tool"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | C65583BE2532085C00A440FD /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastSwiftUpdateCheck = 1200; 97 | LastUpgradeCheck = 1200; 98 | TargetAttributes = { 99 | C65583C52532085C00A440FD = { 100 | CreatedOnToolsVersion = 12.0.1; 101 | }; 102 | }; 103 | }; 104 | buildConfigurationList = C65583C12532085C00A440FD /* Build configuration list for PBXProject "dnc" */; 105 | compatibilityVersion = "Xcode 9.3"; 106 | developmentRegion = en; 107 | hasScannedForEncodings = 0; 108 | knownRegions = ( 109 | en, 110 | Base, 111 | ); 112 | mainGroup = C65583BD2532085C00A440FD; 113 | packageReferences = ( 114 | C65583D12532087B00A440FD /* XCRemoteSwiftPackageReference "swift-argument-parser" */, 115 | ); 116 | productRefGroup = C65583C72532085C00A440FD /* Products */; 117 | projectDirPath = ""; 118 | projectRoot = ""; 119 | targets = ( 120 | C65583C52532085C00A440FD /* dnc */, 121 | ); 122 | }; 123 | /* End PBXProject section */ 124 | 125 | /* Begin PBXSourcesBuildPhase section */ 126 | C65583C22532085C00A440FD /* Sources */ = { 127 | isa = PBXSourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | C65583CA2532085C00A440FD /* main.swift in Sources */, 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXSourcesBuildPhase section */ 135 | 136 | /* Begin XCBuildConfiguration section */ 137 | C65583CB2532085C00A440FD /* Debug */ = { 138 | isa = XCBuildConfiguration; 139 | buildSettings = { 140 | ALWAYS_SEARCH_USER_PATHS = NO; 141 | CLANG_ANALYZER_NONNULL = YES; 142 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 143 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 144 | CLANG_CXX_LIBRARY = "libc++"; 145 | CLANG_ENABLE_MODULES = YES; 146 | CLANG_ENABLE_OBJC_ARC = YES; 147 | CLANG_ENABLE_OBJC_WEAK = YES; 148 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 149 | CLANG_WARN_BOOL_CONVERSION = YES; 150 | CLANG_WARN_COMMA = YES; 151 | CLANG_WARN_CONSTANT_CONVERSION = YES; 152 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 153 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 154 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 155 | CLANG_WARN_EMPTY_BODY = YES; 156 | CLANG_WARN_ENUM_CONVERSION = YES; 157 | CLANG_WARN_INFINITE_RECURSION = YES; 158 | CLANG_WARN_INT_CONVERSION = YES; 159 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 160 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 161 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 162 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 163 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 164 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 165 | CLANG_WARN_STRICT_PROTOTYPES = YES; 166 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 167 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 168 | CLANG_WARN_UNREACHABLE_CODE = YES; 169 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 170 | COPY_PHASE_STRIP = NO; 171 | DEBUG_INFORMATION_FORMAT = dwarf; 172 | ENABLE_STRICT_OBJC_MSGSEND = YES; 173 | ENABLE_TESTABILITY = YES; 174 | GCC_C_LANGUAGE_STANDARD = gnu11; 175 | GCC_DYNAMIC_NO_PIC = NO; 176 | GCC_NO_COMMON_BLOCKS = YES; 177 | GCC_OPTIMIZATION_LEVEL = 0; 178 | GCC_PREPROCESSOR_DEFINITIONS = ( 179 | "DEBUG=1", 180 | "$(inherited)", 181 | ); 182 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 183 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 184 | GCC_WARN_UNDECLARED_SELECTOR = YES; 185 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 186 | GCC_WARN_UNUSED_FUNCTION = YES; 187 | GCC_WARN_UNUSED_VARIABLE = YES; 188 | MACOSX_DEPLOYMENT_TARGET = 10.15; 189 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 190 | MTL_FAST_MATH = YES; 191 | ONLY_ACTIVE_ARCH = YES; 192 | SDKROOT = macosx; 193 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 194 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 195 | }; 196 | name = Debug; 197 | }; 198 | C65583CC2532085C00A440FD /* Release */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ALWAYS_SEARCH_USER_PATHS = NO; 202 | CLANG_ANALYZER_NONNULL = YES; 203 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 204 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 205 | CLANG_CXX_LIBRARY = "libc++"; 206 | CLANG_ENABLE_MODULES = YES; 207 | CLANG_ENABLE_OBJC_ARC = YES; 208 | CLANG_ENABLE_OBJC_WEAK = YES; 209 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 210 | CLANG_WARN_BOOL_CONVERSION = YES; 211 | CLANG_WARN_COMMA = YES; 212 | CLANG_WARN_CONSTANT_CONVERSION = YES; 213 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 221 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 222 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 223 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 224 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 225 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 226 | CLANG_WARN_STRICT_PROTOTYPES = YES; 227 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 228 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 229 | CLANG_WARN_UNREACHABLE_CODE = YES; 230 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 231 | COPY_PHASE_STRIP = NO; 232 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 233 | ENABLE_NS_ASSERTIONS = NO; 234 | ENABLE_STRICT_OBJC_MSGSEND = YES; 235 | GCC_C_LANGUAGE_STANDARD = gnu11; 236 | GCC_NO_COMMON_BLOCKS = YES; 237 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_VARIABLE = YES; 243 | MACOSX_DEPLOYMENT_TARGET = 10.15; 244 | MTL_ENABLE_DEBUG_INFO = NO; 245 | MTL_FAST_MATH = YES; 246 | SDKROOT = macosx; 247 | SWIFT_COMPILATION_MODE = wholemodule; 248 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 249 | }; 250 | name = Release; 251 | }; 252 | C65583CE2532085C00A440FD /* Debug */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | CODE_SIGN_STYLE = Automatic; 256 | DEVELOPMENT_TEAM = 3A6K89K388; 257 | ENABLE_HARDENED_RUNTIME = YES; 258 | PRODUCT_NAME = "$(TARGET_NAME)"; 259 | SWIFT_VERSION = 5.0; 260 | }; 261 | name = Debug; 262 | }; 263 | C65583CF2532085C00A440FD /* Release */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | CODE_SIGN_STYLE = Automatic; 267 | DEVELOPMENT_TEAM = 3A6K89K388; 268 | ENABLE_HARDENED_RUNTIME = YES; 269 | PRODUCT_NAME = "$(TARGET_NAME)"; 270 | SWIFT_VERSION = 5.0; 271 | }; 272 | name = Release; 273 | }; 274 | /* End XCBuildConfiguration section */ 275 | 276 | /* Begin XCConfigurationList section */ 277 | C65583C12532085C00A440FD /* Build configuration list for PBXProject "dnc" */ = { 278 | isa = XCConfigurationList; 279 | buildConfigurations = ( 280 | C65583CB2532085C00A440FD /* Debug */, 281 | C65583CC2532085C00A440FD /* Release */, 282 | ); 283 | defaultConfigurationIsVisible = 0; 284 | defaultConfigurationName = Release; 285 | }; 286 | C65583CD2532085C00A440FD /* Build configuration list for PBXNativeTarget "dnc" */ = { 287 | isa = XCConfigurationList; 288 | buildConfigurations = ( 289 | C65583CE2532085C00A440FD /* Debug */, 290 | C65583CF2532085C00A440FD /* Release */, 291 | ); 292 | defaultConfigurationIsVisible = 0; 293 | defaultConfigurationName = Release; 294 | }; 295 | /* End XCConfigurationList section */ 296 | 297 | /* Begin XCRemoteSwiftPackageReference section */ 298 | C65583D12532087B00A440FD /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = { 299 | isa = XCRemoteSwiftPackageReference; 300 | repositoryURL = "https://github.com/apple/swift-argument-parser"; 301 | requirement = { 302 | kind = upToNextMajorVersion; 303 | minimumVersion = 0.3.1; 304 | }; 305 | }; 306 | /* End XCRemoteSwiftPackageReference section */ 307 | 308 | /* Begin XCSwiftPackageProductDependency section */ 309 | C65583D22532087B00A440FD /* ArgumentParser */ = { 310 | isa = XCSwiftPackageProductDependency; 311 | package = C65583D12532087B00A440FD /* XCRemoteSwiftPackageReference "swift-argument-parser" */; 312 | productName = ArgumentParser; 313 | }; 314 | /* End XCSwiftPackageProductDependency section */ 315 | }; 316 | rootObject = C65583BE2532085C00A440FD /* Project object */; 317 | } 318 | --------------------------------------------------------------------------------