diff --git a/Sources/FengNiao/main.swift b/Sources/FengNiao/main.swift index 92dd6be..063e9e5 100644 --- a/Sources/FengNiao/main.swift +++ b/Sources/FengNiao/main.swift @@ -29,7 +29,7 @@ import Rainbow import FengNiaoKit import PathKit -let appVersion = "0.9.0" +let appVersion = "0.9.1" #if os(Linux) let EX_OK: Int32 = 0 @@ -60,6 +60,11 @@ let isForceOption = BoolOption( helpMessage: "Delete the found unused files without asking.") cli.addOption(isForceOption) +let isDisallowNumericalAffixesOption = BoolOption( + longFlag: "disallow-numerical-affixes", + helpMessage: "Names with numerical affixes without '%' will not count as used if enabled (myImage -> 123myImage456).") +cli.addOption(isDisallowNumericalAffixesOption) + let excludePathOption = MultiStringOption( shortFlag: "e", longFlag: "exclude", helpMessage: "Exclude paths from search.") @@ -120,11 +125,13 @@ let isForce = isForceOption.value let excludePaths = excludePathOption.value ?? [] let resourceExtentions = resourceExtOption.value ?? ["imageset", "jpg", "png", "gif", "pdf"] let fileExtensions = fileExtOption.value ?? ["h", "m", "mm", "swift", "xib", "storyboard", "plist"] +let isDisallowNumericalAffixes = isDisallowNumericalAffixesOption.value let fengNiao = FengNiao(projectPath: projectPath, excludedPaths: excludePaths, resourceExtensions: resourceExtentions, - searchInFileExtensions: fileExtensions) + searchInFileExtensions: fileExtensions, + disallowNumericalAffixes: isDisallowNumericalAffixes) let unusedFiles: [FileInfo] do { diff --git a/Sources/FengNiaoKit/FengNiao.swift b/Sources/FengNiaoKit/FengNiao.swift index ef7421b..e2d1586 100644 --- a/Sources/FengNiaoKit/FengNiao.swift +++ b/Sources/FengNiaoKit/FengNiao.swift @@ -100,18 +100,20 @@ public struct FengNiao { let excludedPaths: [Path] let resourceExtensions: [String] let searchInFileExtensions: [String] - + let disallowNumericalAffixes: Bool + let regularDirExtensions = ["imageset", "launchimage", "appiconset", "stickersiconset", "complicationset", "bundle"] var nonDirExtensions: [String] { return resourceExtensions.filter { !regularDirExtensions.contains($0) } } - public init(projectPath: String, excludedPaths: [String], resourceExtensions: [String], searchInFileExtensions: [String]) { + public init(projectPath: String, excludedPaths: [String], resourceExtensions: [String], searchInFileExtensions: [String], disallowNumericalAffixes: Bool) { let path = Path(projectPath).absolute() self.projectPath = path self.excludedPaths = excludedPaths.map { path + Path($0) } self.resourceExtensions = resourceExtensions self.searchInFileExtensions = searchInFileExtensions + self.disallowNumericalAffixes = disallowNumericalAffixes } public func unusedFiles() throws -> [FileInfo] { @@ -125,7 +127,7 @@ public struct FengNiao { let allResources = allResourceFiles() let usedNames = allUsedStringNames() - return FengNiao.filterUnused(from: allResources, used: usedNames).map( FileInfo.init ) + return FengNiao.filterUnused(from: allResources, used: usedNames, disallowNumericalAffixes: disallowNumericalAffixes).map( FileInfo.init ) } // Return a failed list of deleting @@ -253,10 +255,10 @@ public struct FengNiao { return Set(result) } - static func filterUnused(from all: [String: Set], used: Set) -> Set { + static func filterUnused(from all: [String: Set], used: Set, disallowNumericalAffixes: Bool) -> Set { let unusedPairs = all.filter { key, _ in return !used.contains(key) && - !used.contains { $0.similarPatternWithNumberIndex(other: key) } + !used.contains { $0.similarPatternWithNumberIndex(other: key, disallowNumericalAffixes: disallowNumericalAffixes) } } return Set( unusedPairs.flatMap { $0.value } ) } @@ -265,9 +267,10 @@ public struct FengNiao { let digitalRex = try! NSRegularExpression(pattern: "(\\d+)", options: .caseInsensitive) extension String { - func similarPatternWithNumberIndex(other: String) -> Bool { + func similarPatternWithNumberIndex(other: String, disallowNumericalAffixes: Bool) -> Bool { // self -> pattern "image%02d" // other -> name "image01" + guard !disallowNumericalAffixes || contains("%") else { return false } let matches = digitalRex.matches(in: other, options: [], range: other.fullRange) // No digital found in resource key. guard matches.count >= 1 else { return false } diff --git a/Tests/FengNiaoKitTests/FengNiaoKitSpec.swift b/Tests/FengNiaoKitTests/FengNiaoKitSpec.swift index 09d5c14..05e29bb 100644 --- a/Tests/FengNiaoKitTests/FengNiaoKitSpec.swift +++ b/Tests/FengNiaoKitTests/FengNiaoKitSpec.swift @@ -176,7 +176,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: ["png", "jpg"], - searchInFileExtensions: ["txt"]) + searchInFileExtensions: ["txt"], + disallowNumericalAffixes: false) let result = fengniao.allUsedStringNames() let expected: Set = ["image", "another-image"] @@ -188,7 +189,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: ["png", "jpg"], - searchInFileExtensions: ["swift"]) + searchInFileExtensions: ["swift"], + disallowNumericalAffixes: false) let result = fengniao.allUsedStringNames() let expected: Set = ["common.login", "common.logout", @@ -205,7 +207,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: ["png", "jpg"], - searchInFileExtensions: ["m", "mm"]) + searchInFileExtensions: ["m", "mm"], + disallowNumericalAffixes: false) let result = fengniao.allUsedStringNames() let expected: Set = ["info.error.memory.full.confirm", "info.error.memory.full.ios", @@ -222,7 +225,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: ["png"], - searchInFileExtensions: ["xib", "storyboard"]) + searchInFileExtensions: ["xib", "storyboard"], + disallowNumericalAffixes: false) let result = fengniao.allUsedStringNames() let expected: Set = ["profile_image", "bottom", @@ -239,7 +243,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: [], - searchInFileExtensions: ["plist"]) + searchInFileExtensions: ["plist"], + disallowNumericalAffixes: false) let result = fengniao.allUsedStringNames() let expected: Set = ["some_image", "some_type", "Some Image"] @@ -251,7 +256,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: [], - searchInFileExtensions: ["pbxproj"]) + searchInFileExtensions: ["pbxproj"], + disallowNumericalAffixes: false) let result = fengniao.allUsedStringNames() let expected: Set = ["AppIcon", "MessagesIcon", "iMessage App Icon", "Complication"] @@ -263,7 +269,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: ["png", "jpg"], - searchInFileExtensions: ["m", "xib"]) + searchInFileExtensions: ["m", "xib"], + disallowNumericalAffixes: false) let result = fengniao.allUsedStringNames() let expected: Set = ["profile_image", "bottom", @@ -280,7 +287,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: ["SubFolder2"], resourceExtensions: ["png", "jpg"], - searchInFileExtensions: ["m", "xib"]) + searchInFileExtensions: ["m", "xib"], + disallowNumericalAffixes: false) let result = fengniao.allUsedStringNames() let expected: Set = ["info.error.memory.full.confirm", "info.error.memory.full.ios", @@ -296,7 +304,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: ["png", "jpg", "imageset"], - searchInFileExtensions: []) + searchInFileExtensions: [], + disallowNumericalAffixes: false) let result = fengniao.allResourceFiles() let expected: [String: Set] = [ "file1": [(project + "file1.png").string], @@ -311,7 +320,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: ["Ignore"], resourceExtensions: ["png", "jpg", "imageset"], - searchInFileExtensions: []) + searchInFileExtensions: [], + disallowNumericalAffixes: false) let result = fengniao.allResourceFiles() let expected: [String: Set] = [ "normal": [(project + "normal.png").string], @@ -325,7 +335,8 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { let fengniao = FengNiao(projectPath: project.string, excludedPaths: [], resourceExtensions: ["png"], - searchInFileExtensions: []) + searchInFileExtensions: [], + disallowNumericalAffixes: false) let result = fengniao.allResourceFiles() let expected: [String: Set] = [ "image": [(project + "image@2x.png").string, (project + "image@3x.png").string], @@ -344,7 +355,7 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { ] let used: Set = ["book"] - let result = FengNiao.filterUnused(from: all, used: used) + let result = FengNiao.filterUnused(from: all, used: used, disallowNumericalAffixes: false) let expected: Set = ["face.png", "moon.png"] try expect(result) == expected @@ -358,11 +369,11 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { ] let used: Set = ["book"] - let result = FengNiao.filterUnused(from: all, used: used) + let result = FengNiao.filterUnused(from: all, used: used, disallowNumericalAffixes: false) let expected: Set = ["face1.png", "face2.png", "moon.png"] try expect(result) == expected } - + $0.it("should not filter similar pattern") { let all: [String: Set] = [ "face": ["face.png"], @@ -383,14 +394,55 @@ public let testFengNiaoKit: ((ContextType) -> Void) = { "unused_3": ["unused_3.png"], ] let used: Set = ["image%02d", "%d_set", "middle_%d_bird","suffix_"] - let result = FengNiao.filterUnused(from: all, used: used) + let result = FengNiao.filterUnused(from: all, used: used, disallowNumericalAffixes: false) let expected: Set = ["face.png", "unused_1.png", "unused_2.png", "unused_3.png"] try expect(result) == expected } } - + + $0.describe("FengNiao File Filter with allowNumericalAffixes") { + $0.it("should filter files with affixes correctly when allowed") { + let all: [String: Set] = [ + "suffix20": ["suffix20.png", "suffix20@2x.png"], + "50prefix": ["50prefix.png", "50prefix@2x.png"], + "10both20": ["10both20.png", "10both20@2x.png"], + ] + let used: Set = ["suffix", "prefix", "both"] + + let result = FengNiao.filterUnused(from: all, used: used, disallowNumericalAffixes: false) + let expected: Set = ["10both20@2x.png", "10both20.png"] + try expect(result) == expected + } + + $0.it("should filter files with affixes correctly when not allowed") { + let all: [String: Set] = [ + "suffix20": ["suffix20.png", "suffix20@2x.png"], + "50prefix": ["50prefix.png", "50prefix@2x.png"], + "10both20": ["10both20.png", "10both20@2x.png"], + ] + let used: Set = ["suffix", "prefix", "both"] + + let result = FengNiao.filterUnused(from: all, used: used, disallowNumericalAffixes: true) + let expected: Set = ["suffix20.png", "suffix20@2x.png", "50prefix.png", "50prefix@2x.png", "10both20@2x.png", "10both20.png"] + try expect(result) == expected + } + + $0.it("should filter files with percent sign correctly when not allowed") { + let all: [String: Set] = [ + "suffix20": ["suffix20.png", "suffix20@2x.png"], + "50prefix": ["50prefix.png", "50prefix@2x.png"], + "10both20": ["10both20.png", "10both20@2x.png"], + ] + let used: Set = ["suffix%d", "%dprefix", "%dboth%d"] + + let result = FengNiao.filterUnused(from: all, used: used, disallowNumericalAffixes: true) + let expected: Set = ["10both20@2x.png", "10both20.png"] + try expect(result) == expected + } + } + $0.describe("FengNiao Deleting File") { - + let file = fixtures + "DeleteFiles/file_in_root" let folder = fixtures + "DeleteFiles/Folder"