Skip to content

Commit 421e435

Browse files
committed
refactor: detect and display output path conflicts
Enhance `discoverAndValidateConfigs` to detect output path conflicts and return them alongside valid configs. In `executeBatch` and `displayBatchStartInfo`, add logic to handle and display path conflicts for better user awareness during batch processing. This change improves transparency about potential resource conflicts between config files.
1 parent f28c0e0 commit 421e435

2 files changed

Lines changed: 32 additions & 14 deletions

File tree

Sources/ExFigCLI/Subcommands/Batch.swift

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ extension ExFigCommand {
9191
let ui = ExFigCommand.terminalUI!
9292

9393
// Discover and validate configs
94-
let validConfigs = try discoverAndValidateConfigs(ui: ui)
94+
let (validConfigs, conflicts) = try discoverAndValidateConfigs(ui: ui)
9595
guard !validConfigs.isEmpty else { return }
9696

9797
// Prepare configs with checkpoint handling
@@ -111,6 +111,7 @@ extension ExFigCommand {
111111
// Execute batch
112112
let (result, rateLimiter) = await executeBatch(
113113
configs: configs,
114+
conflicts: conflicts,
114115
checkpoint: checkpoint,
115116
workingDirectory: workingDirectory,
116117
ui: ui
@@ -127,11 +128,13 @@ extension ExFigCommand {
127128

128129
// MARK: - Run Helpers
129130

130-
private func discoverAndValidateConfigs(ui: TerminalUI) throws -> [URL] {
131+
private func discoverAndValidateConfigs(
132+
ui: TerminalUI
133+
) throws -> (configs: [URL], conflicts: [OutputPathConflict]) {
131134
let configURLs = try discoverConfigs(ui: ui)
132135
guard !configURLs.isEmpty else {
133136
ui.warning(.noConfigsFound)
134-
return []
137+
return ([], [])
135138
}
136139

137140
let discovery = ConfigDiscovery()
@@ -144,17 +147,11 @@ extension ExFigCommand {
144147

145148
guard !validConfigs.isEmpty else {
146149
ui.warning(.noValidConfigs)
147-
return []
150+
return ([], [])
148151
}
149152

150-
// Check for conflicts
151153
let conflicts = try discovery.detectOutputPathConflicts(validConfigs)
152-
if !conflicts.isEmpty {
153-
let formatter = ConflictFormatter()
154-
formatter.display(conflicts)
155-
}
156-
157-
return validConfigs
154+
return (validConfigs, conflicts)
158155
}
159156

160157
private func prepareConfigsWithCheckpoint(
@@ -199,6 +196,7 @@ extension ExFigCommand {
199196
// swiftlint:disable:next function_body_length
200197
private func executeBatch(
201198
configs: [ConfigFile],
199+
conflicts: [OutputPathConflict],
202200
checkpoint: BatchCheckpoint,
203201
workingDirectory: URL,
204202
ui: TerminalUI
@@ -254,6 +252,7 @@ extension ExFigCommand {
254252

255253
displayBatchStartInfo(
256254
configs: configs,
255+
conflicts: conflicts,
257256
sharedGranularCache: sharedGranularCache,
258257
ui: ui
259258
)
@@ -403,14 +402,33 @@ extension ExFigCommand {
403402
/// Displays batch start information.
404403
private func displayBatchStartInfo(
405404
configs: [ConfigFile],
405+
conflicts: [OutputPathConflict],
406406
sharedGranularCache: SharedGranularCache?,
407407
ui: TerminalUI
408408
) {
409409
ui.info("Processing \(configs.count) config(s) with up to \(parallel) parallel workers:")
410410

411-
let headers: [TableCellStyle] = [.plain("#"), .plain("Config")]
411+
// Build conflict lookup: config name → shared path
412+
var conflictMap: [String: String] = [:]
413+
for conflict in conflicts {
414+
for configURL in conflict.configs {
415+
conflictMap[configURL.lastPathComponent] = conflict.path
416+
}
417+
}
418+
419+
let hasConflicts = !conflicts.isEmpty
420+
let headers: [TableCellStyle] = hasConflicts
421+
? [.plain("#"), .plain("Config"), .plain("Conflict")]
422+
: [.plain("#"), .plain("Config")]
423+
412424
let rows: [StyledTableRow] = configs.enumerated().map { index, config in
413-
[.plain("\(index + 1)"), .plain(config.name)]
425+
if hasConflicts {
426+
let conflictPath = conflictMap[config.name]
427+
let conflictCell: TableCellStyle = conflictPath.map { .warning($0) } ?? .plain("")
428+
return [.plain("\(index + 1)"), .plain(config.name), conflictCell]
429+
} else {
430+
return [.plain("\(index + 1)"), .plain(config.name)]
431+
}
414432
}
415433
NooraUI.shared.table(headers: headers, rows: rows)
416434

Tests/ExFigTests/PKL/PKLEvaluatorTests.swift

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

5-
@Suite("PKLEvaluator Tests")
5+
@Suite("PKLEvaluator Tests", .serialized, .timeLimit(.minutes(2)))
66
struct PKLEvaluatorTests {
77
/// Path to test fixtures
88
static let fixturesPath = URL(fileURLWithPath: #filePath)

0 commit comments

Comments
 (0)