├── 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 |
--------------------------------------------------------------------------------