Skip to content

Commit 111a67c

Browse files
authored
refactor: extract constants and file icon helpers (#22)
- Create Constants.swift for Docker, Keychain, GitHub, UI values - Create FileIconHelper.swift for file icon/color/language mapping - Remove duplicate code from EditorView and FileExplorerView - All 22 tests passing
1 parent 5278ec0 commit 111a67c

File tree

7 files changed

+136
-144
lines changed

7 files changed

+136
-144
lines changed

Sources/Zero/Constants.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Foundation
2+
3+
enum Constants {
4+
enum Docker {
5+
static let path = "/usr/local/bin/docker"
6+
static let baseImage = "alpine:latest"
7+
static let workspacePath = "/workspace"
8+
}
9+
10+
enum Keychain {
11+
static let service = "com.zero.ide"
12+
static let account = "github_token"
13+
}
14+
15+
enum GitHub {
16+
static let pageSize = 30
17+
}
18+
19+
enum UI {
20+
static let sidebarMinWidth: CGFloat = 220
21+
static let sidebarIdealWidth: CGFloat = 260
22+
static let sidebarMaxWidth: CGFloat = 400
23+
}
24+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import SwiftUI
2+
3+
enum FileIconHelper {
4+
/// 파일 확장자에 따른 아이콘 이름과 색상 반환
5+
static func iconInfo(for filename: String, isDirectory: Bool) -> (name: String, color: Color) {
6+
if isDirectory {
7+
return ("folder.fill", .yellow)
8+
}
9+
10+
let ext = (filename as NSString).pathExtension.lowercased()
11+
let name = filename.lowercased()
12+
13+
// Special files
14+
if name == "dockerfile" { return ("shippingbox.fill", .blue) }
15+
if name == "readme.md" { return ("book.fill", .blue) }
16+
if name == ".gitignore" { return ("eye.slash", .orange) }
17+
if name.contains("license") { return ("doc.text.fill", .green) }
18+
19+
switch ext {
20+
case "swift": return ("swift", .orange)
21+
case "java": return ("cup.and.saucer.fill", .red)
22+
case "kt", "kts": return ("k.square.fill", .purple)
23+
case "js": return ("j.square.fill", .yellow)
24+
case "ts": return ("t.square.fill", .blue)
25+
case "py": return ("p.square.fill", .cyan)
26+
case "rb": return ("r.square.fill", .red)
27+
case "go": return ("g.square.fill", .cyan)
28+
case "rs": return ("r.square.fill", .orange)
29+
case "json": return ("curlybraces", .yellow)
30+
case "xml", "plist": return ("chevron.left.forwardslash.chevron.right", .orange)
31+
case "html": return ("globe", .orange)
32+
case "css", "scss", "sass": return ("paintbrush.fill", .pink)
33+
case "md", "markdown": return ("doc.richtext.fill", .blue)
34+
case "yml", "yaml": return ("list.bullet.rectangle.fill", .pink)
35+
case "sh", "bash", "zsh": return ("terminal.fill", .green)
36+
case "sql": return ("cylinder.fill", .blue)
37+
case "png", "jpg", "jpeg", "gif", "svg", "ico": return ("photo.fill", .purple)
38+
case "pdf": return ("doc.fill", .red)
39+
case "zip", "tar", "gz", "rar": return ("doc.zipper", .gray)
40+
case "gradle": return ("g.square.fill", .green)
41+
case "properties": return ("gearshape.fill", .gray)
42+
case "env": return ("key.fill", .yellow)
43+
case "lock": return ("lock.fill", .gray)
44+
default: return ("doc.fill", .secondary)
45+
}
46+
}
47+
48+
/// 파일 확장자에 따른 언어 이름 반환 (syntax highlighting용)
49+
static func languageName(for filename: String) -> String {
50+
let ext = (filename as NSString).pathExtension.lowercased()
51+
switch ext {
52+
case "swift": return "swift"
53+
case "js": return "javascript"
54+
case "ts": return "typescript"
55+
case "py": return "python"
56+
case "java": return "java"
57+
case "kt", "kts": return "kotlin"
58+
case "json": return "json"
59+
case "html": return "html"
60+
case "css": return "css"
61+
case "md": return "markdown"
62+
case "yaml", "yml": return "yaml"
63+
case "xml": return "xml"
64+
case "sh": return "shell"
65+
case "c", "h": return "c"
66+
case "cpp", "hpp": return "cpp"
67+
case "go": return "go"
68+
case "rs": return "rust"
69+
case "rb": return "ruby"
70+
case "php": return "php"
71+
case "sql": return "sql"
72+
case "dockerfile": return "dockerfile"
73+
default: return "plaintext"
74+
}
75+
}
76+
77+
/// 언어 표시 이름 반환
78+
static func languageDisplayName(_ lang: String) -> String {
79+
switch lang {
80+
case "swift": return "Swift"
81+
case "java": return "Java"
82+
case "kotlin": return "Kotlin"
83+
case "javascript": return "JavaScript"
84+
case "typescript": return "TypeScript"
85+
case "python": return "Python"
86+
case "json": return "JSON"
87+
case "html": return "HTML"
88+
case "css": return "CSS"
89+
case "markdown": return "Markdown"
90+
case "yaml": return "YAML"
91+
case "shell": return "Shell"
92+
case "dockerfile": return "Dockerfile"
93+
case "plaintext": return "Plain Text"
94+
default: return lang.capitalized
95+
}
96+
}
97+
}

Sources/Zero/Services/ContainerOrchestrator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extension DockerService: DockerRunning {}
1212
class ContainerOrchestrator {
1313
private let dockerService: DockerRunning
1414
private let sessionManager: SessionManager
15-
private let baseImage = "alpine:latest"
15+
private let baseImage = Constants.Docker.baseImage
1616

1717
init(dockerService: DockerRunning, sessionManager: SessionManager) {
1818
self.dockerService = dockerService

Sources/Zero/Services/DockerService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
struct DockerService {
44
let runner: CommandRunning
5-
let dockerPath = "/usr/local/bin/docker" // 추후 환경변수 등에서 탐색 가능
5+
let dockerPath = Constants.Docker.path
66

77
init(runner: CommandRunning = CommandRunner()) {
88
self.runner = runner

Sources/Zero/Views/AppState.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ class AppState: ObservableObject {
1919
@Published var selectedOrg: Organization? = nil
2020

2121
// 페이지 크기 (테스트 시 조정 가능)
22-
var pageSize: Int = 30
22+
var pageSize: Int = Constants.GitHub.pageSize
2323

24-
private let keychainService = "com.zero.ide"
25-
private let keychainAccount = "github_token"
24+
private let keychainService = Constants.Keychain.service
25+
private let keychainAccount = Constants.Keychain.account
2626
private let sessionManager = SessionManager()
2727
private lazy var orchestrator: ContainerOrchestrator = {
2828
let docker = DockerService()

Sources/Zero/Views/EditorView.swift

Lines changed: 6 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ struct EditorView: View {
4545
.foregroundStyle(.secondary)
4646

4747
if let file = selectedFile {
48+
let iconInfo = FileIconHelper.iconInfo(for: file.name, isDirectory: false)
49+
4850
Image(systemName: "chevron.right")
4951
.font(.system(size: 9))
5052
.foregroundStyle(.quaternary)
5153

52-
Image(systemName: iconForFile(file.name))
54+
Image(systemName: iconInfo.name)
5355
.font(.system(size: 12))
54-
.foregroundStyle(colorForFile(file.name))
56+
.foregroundStyle(iconInfo.color)
5557

5658
Text(file.name)
5759
.font(.system(size: 13, weight: .medium))
@@ -100,7 +102,7 @@ struct EditorView: View {
100102
HStack(spacing: 4) {
101103
Image(systemName: "chevron.left.forwardslash.chevron.right")
102104
.font(.system(size: 9))
103-
Text(languageDisplayName(currentLanguage))
105+
Text(FileIconHelper.languageDisplayName(currentLanguage))
104106
.font(.system(size: 11))
105107
}
106108
.foregroundStyle(.secondary)
@@ -145,68 +147,11 @@ struct EditorView: View {
145147

146148
// MARK: - Helpers
147149

148-
private func iconForFile(_ filename: String) -> String {
149-
let ext = (filename as NSString).pathExtension.lowercased()
150-
switch ext {
151-
case "swift": return "swift"
152-
case "java", "kt", "kts": return "cup.and.saucer.fill"
153-
case "js": return "j.square.fill"
154-
case "ts": return "t.square.fill"
155-
case "py": return "p.square.fill"
156-
case "json": return "curlybraces"
157-
case "md": return "doc.richtext.fill"
158-
case "html", "css": return "globe"
159-
case "yml", "yaml": return "list.bullet.rectangle.fill"
160-
case "sh": return "terminal.fill"
161-
case "dockerfile": return "shippingbox.fill"
162-
default: return "doc.text.fill"
163-
}
164-
}
165-
166-
private func colorForFile(_ filename: String) -> Color {
167-
let ext = (filename as NSString).pathExtension.lowercased()
168-
switch ext {
169-
case "swift": return .orange
170-
case "java": return .red
171-
case "kt", "kts": return .purple
172-
case "js": return .yellow
173-
case "ts": return .blue
174-
case "py": return .cyan
175-
case "json": return .yellow
176-
case "md": return .blue
177-
case "html": return .orange
178-
case "css": return .pink
179-
case "yml", "yaml": return .pink
180-
case "sh": return .green
181-
default: return .secondary
182-
}
183-
}
184-
185-
private func languageDisplayName(_ lang: String) -> String {
186-
switch lang {
187-
case "swift": return "Swift"
188-
case "java": return "Java"
189-
case "kotlin": return "Kotlin"
190-
case "javascript": return "JavaScript"
191-
case "typescript": return "TypeScript"
192-
case "python": return "Python"
193-
case "json": return "JSON"
194-
case "html": return "HTML"
195-
case "css": return "CSS"
196-
case "markdown": return "Markdown"
197-
case "yaml": return "YAML"
198-
case "shell": return "Shell"
199-
case "dockerfile": return "Dockerfile"
200-
case "plaintext": return "Plain Text"
201-
default: return lang.capitalized
202-
}
203-
}
204-
205150
private func loadFile(_ file: FileItem) {
206151
guard !file.isDirectory else { return }
207152

208153
isLoadingFile = true
209-
currentLanguage = detectLanguage(for: file.name)
154+
currentLanguage = FileIconHelper.languageName(for: file.name)
210155

211156
Task {
212157
do {
@@ -253,32 +198,4 @@ struct EditorView: View {
253198
}
254199
}
255200
}
256-
257-
private func detectLanguage(for filename: String) -> String {
258-
let ext = (filename as NSString).pathExtension.lowercased()
259-
switch ext {
260-
case "swift": return "swift"
261-
case "js": return "javascript"
262-
case "ts": return "typescript"
263-
case "py": return "python"
264-
case "java": return "java"
265-
case "kt", "kts": return "kotlin"
266-
case "json": return "json"
267-
case "html": return "html"
268-
case "css": return "css"
269-
case "md": return "markdown"
270-
case "yaml", "yml": return "yaml"
271-
case "xml": return "xml"
272-
case "sh": return "shell"
273-
case "c", "h": return "c"
274-
case "cpp", "hpp": return "cpp"
275-
case "go": return "go"
276-
case "rs": return "rust"
277-
case "rb": return "ruby"
278-
case "php": return "php"
279-
case "sql": return "sql"
280-
case "dockerfile": return "dockerfile"
281-
default: return "plaintext"
282-
}
283-
}
284201
}

Sources/Zero/Views/FileExplorerView.swift

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -226,56 +226,10 @@ struct FileRowView: View {
226226

227227
@ViewBuilder
228228
private var fileIconView: some View {
229-
let (iconName, iconColor) = fileIconInfo(for: file)
230-
231-
if file.isDirectory {
232-
Image(systemName: isExpanded ? "folder.fill" : "folder.fill")
233-
.font(.system(size: 14))
234-
.foregroundStyle(Color.yellow)
235-
} else {
236-
Image(systemName: iconName)
237-
.font(.system(size: 13))
238-
.foregroundStyle(iconColor)
239-
}
240-
}
241-
242-
private func fileIconInfo(for file: FileItem) -> (String, Color) {
243-
let ext = (file.name as NSString).pathExtension.lowercased()
244-
let name = file.name.lowercased()
245-
246-
// Special files
247-
if name == "dockerfile" { return ("shippingbox.fill", .blue) }
248-
if name == "readme.md" { return ("book.fill", .blue) }
249-
if name == ".gitignore" { return ("eye.slash", .orange) }
250-
if name.contains("license") { return ("doc.text.fill", .green) }
251-
252-
switch ext {
253-
case "swift": return ("swift", .orange)
254-
case "java": return ("cup.and.saucer.fill", .red)
255-
case "kt", "kts": return ("k.square.fill", .purple)
256-
case "js": return ("j.square.fill", .yellow)
257-
case "ts": return ("t.square.fill", .blue)
258-
case "py": return ("p.square.fill", .cyan)
259-
case "rb": return ("r.square.fill", .red)
260-
case "go": return ("g.square.fill", .cyan)
261-
case "rs": return ("r.square.fill", .orange)
262-
case "json": return ("curlybraces", .yellow)
263-
case "xml", "plist": return ("chevron.left.forwardslash.chevron.right", .orange)
264-
case "html": return ("globe", .orange)
265-
case "css", "scss", "sass": return ("paintbrush.fill", .pink)
266-
case "md", "markdown": return ("doc.richtext.fill", .blue)
267-
case "yml", "yaml": return ("list.bullet.rectangle.fill", .pink)
268-
case "sh", "bash", "zsh": return ("terminal.fill", .green)
269-
case "sql": return ("cylinder.fill", .blue)
270-
case "png", "jpg", "jpeg", "gif", "svg", "ico": return ("photo.fill", .purple)
271-
case "pdf": return ("doc.fill", .red)
272-
case "zip", "tar", "gz", "rar": return ("doc.zipper", .gray)
273-
case "gradle": return ("g.square.fill", .green)
274-
case "properties": return ("gearshape.fill", .gray)
275-
case "env": return ("key.fill", .yellow)
276-
case "lock": return ("lock.fill", .gray)
277-
default: return ("doc.fill", .secondary)
278-
}
229+
let info = FileIconHelper.iconInfo(for: file.name, isDirectory: file.isDirectory)
230+
Image(systemName: info.name)
231+
.font(.system(size: file.isDirectory ? 14 : 13))
232+
.foregroundStyle(info.color)
279233
}
280234

281235
private func toggleExpand() {

0 commit comments

Comments
 (0)