Skip to content

Commit c02f19b

Browse files
authored
Merge pull request #90 from onevcat/cli/argument-parser
Migrate CLI to swift-argument-parser
2 parents 3da3955 + de183ae commit c02f19b

5 files changed

Lines changed: 264 additions & 259 deletions

File tree

Package.resolved

Lines changed: 39 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ let package = Package(
1111
],
1212
dependencies: [
1313
.package(url: "https://github.com/onevcat/Rainbow.git", from: "3.1.1"),
14-
.package(url: "https://github.com/benoit-pereira-da-silva/CommandLine.git", from: "4.0.0"),
14+
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.4.0"),
1515
.package(url: "https://github.com/kylef/PathKit.git", from: "1.0.1")
1616
],
1717
targets: [
@@ -20,9 +20,10 @@ let package = Package(
2020
name: "FengNiao",
2121
dependencies: [
2222
"FengNiaoKit",
23-
.product(name: "CommandLineKit", package: "CommandLine")
23+
.product(name: "ArgumentParser", package: "swift-argument-parser")
2424
]
2525
),
26-
.testTarget(name: "FengNiaoKitTests", dependencies: ["FengNiaoKit"], exclude: ["../Fixtures"])
26+
.testTarget(name: "FengNiaoKitTests", dependencies: ["FengNiaoKit"], exclude: ["../Fixtures"]),
27+
.testTarget(name: "FengNiaoCLITests", dependencies: ["FengNiao"])
2728
]
2829
)

Sources/FengNiao/CLI.swift

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import ArgumentParser
2+
import Foundation
3+
import Rainbow
4+
import FengNiaoKit
5+
import PathKit
6+
7+
@main
8+
struct FengNiaoCommand: ParsableCommand {
9+
private static let appVersion = "0.11.0"
10+
private static let exitUnusedResources: Int32 = 1
11+
private static let exitUsage: Int32 = 64
12+
13+
static let configuration = CommandConfiguration(
14+
commandName: "fengniao",
15+
abstract: "Find and delete unused resources in Xcode projects.",
16+
version: appVersion
17+
)
18+
19+
@Option(
20+
name: .shortAndLong,
21+
help: "Root path of your Xcode project. Default is current folder."
22+
)
23+
var project: String = "."
24+
25+
@Flag(
26+
name: .long,
27+
help: "Delete the found unused files without asking."
28+
)
29+
var force: Bool = false
30+
31+
@Option(
32+
name: [.short, .long],
33+
parsing: .upToNextOption,
34+
help: "Exclude paths from search."
35+
)
36+
var exclude: [String] = []
37+
38+
@Option(
39+
name: [.short, .long],
40+
parsing: .upToNextOption,
41+
help: "Resource file extensions need to be searched. Default is 'imageset jpg png gif pdf'"
42+
)
43+
var resourceExtensions: [String] = ["imageset", "jpg", "png", "gif", "pdf"]
44+
45+
@Option(
46+
name: [.short, .long],
47+
parsing: .upToNextOption,
48+
help: "In which types of files we should search for resource usage. Default is 'm mm swift xib storyboard plist'"
49+
)
50+
var fileExtensions: [String] = ["h", "m", "mm", "swift", "xib", "storyboard", "plist"]
51+
52+
@Flag(
53+
name: .long,
54+
help: "Skip the Project file (.pbxproj) reference cleaning. By skipping it, the project file will be left untouched. You may want to skip ths step if you are trying to build multiple projects with dependency and keep .pbxproj unchanged while compiling."
55+
)
56+
var skipProjReference: Bool = false
57+
58+
@Flag(
59+
name: .long,
60+
help: "Print results as xcode warnings and return non zero code if any."
61+
)
62+
var xcodeWarnings: Bool = false
63+
64+
@Flag(
65+
name: .long,
66+
help: "List unused files and exit without prompting."
67+
)
68+
var listOnly: Bool = false
69+
70+
func run() throws {
71+
let fengNiao = FengNiao(
72+
projectPath: project,
73+
excludedPaths: exclude,
74+
resourceExtensions: resourceExtensions,
75+
searchInFileExtensions: fileExtensions
76+
)
77+
78+
let unusedFiles: [FileInfo]
79+
do {
80+
print("Searching unused file. This may take a while...")
81+
unusedFiles = try fengNiao.unusedFiles()
82+
} catch {
83+
guard let e = error as? FengNiaoError else {
84+
print("Unknown Error: \(error)".red.bold)
85+
throw ExitCode(Self.exitUsage)
86+
}
87+
switch e {
88+
case .noResourceExtension:
89+
print("You need to specify some resource extensions as search target. Use --resource-extensions to specify.".red.bold)
90+
case .noFileExtension:
91+
print("You need to specify some file extensions to search in. Use --file-extensions to specify.".red.bold)
92+
}
93+
throw ExitCode(Self.exitUsage)
94+
}
95+
96+
if unusedFiles.isEmpty {
97+
print("😎 Hu, you have no unused resources in path: \(Path(project).absolute()).".green.bold)
98+
return
99+
}
100+
101+
if xcodeWarnings {
102+
for file in unusedFiles.sorted(by: { $0.size > $1.size }) {
103+
print("\(file.path.string): warning: Unused resource of size \(file.readableSize)")
104+
}
105+
throw ExitCode(Self.exitUnusedResources)
106+
}
107+
108+
if listOnly {
109+
let size = unusedFiles.reduce(0) { $0 + $1.size }.fn_readableSize
110+
for file in unusedFiles.sorted(by: { $0.size > $1.size }) {
111+
print("\(file.readableSize) \(file.path.string)")
112+
}
113+
print("\(unusedFiles.count) unused files are found. Total Size: \(size)".yellow.bold)
114+
return
115+
}
116+
117+
if !force {
118+
var result = promptResult(files: unusedFiles)
119+
while result == .list {
120+
for file in unusedFiles.sorted(by: { $0.size > $1.size }) {
121+
print("\(file.readableSize) \(file.path.string)")
122+
}
123+
result = promptResult(files: unusedFiles)
124+
}
125+
126+
switch result {
127+
case .list:
128+
fatalError()
129+
case .delete:
130+
break
131+
case .ignore:
132+
print("Ignored. Nothing to do, bye!".green.bold)
133+
return
134+
}
135+
}
136+
137+
print("Deleting unused files...⚙".bold)
138+
139+
let (deleted, failed) = FengNiao.delete(unusedFiles)
140+
guard failed.isEmpty else {
141+
print("\(unusedFiles.count - failed.count) unused files are deleted. But we encountered some error while deleting these \(failed.count) files:".yellow.bold)
142+
for (fileInfo, err) in failed {
143+
print("\(fileInfo.path.string) - \(err.localizedDescription)")
144+
}
145+
throw ExitCode(Self.exitUsage)
146+
}
147+
148+
print("\(unusedFiles.count) unused files are deleted.".green.bold)
149+
150+
if !skipProjReference {
151+
if let children = try? Path(project).absolute().children() {
152+
print("Now Deleting unused Reference in project.pbxproj...⚙".bold)
153+
for path in children where path.lastComponent.hasSuffix("xcodeproj") {
154+
let pbxproj = path + "project.pbxproj"
155+
FengNiao.deleteReference(projectFilePath: pbxproj, deletedFiles: deleted)
156+
}
157+
print("Unused Reference deleted successfully.".green.bold)
158+
}
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)