Skip to content

Commit 345bf01

Browse files
feat: color and plain modes for --progress flag
Signed-off-by: Karan <karanlokchandani@protonmail.com>
1 parent 1c0e988 commit 345bf01

File tree

8 files changed

+72
-23
lines changed

8 files changed

+72
-23
lines changed

Sources/ContainerClient/Flags.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,11 @@ public struct Flags {
210210
public enum ProgressType: String, ExpressibleByArgument {
211211
case none
212212
case ansi
213+
case plain
214+
case color
213215
}
214216

215-
@Option(name: .long, help: ArgumentHelp("Progress type (format: none|ansi)", valueName: "type"))
217+
@Option(name: .long, help: ArgumentHelp("Progress type (format: none|ansi|plain|color)", valueName: "type"))
216218
public var progress: ProgressType = .ansi
217219
}
218220
}

Sources/ContainerCommands/Container/ContainerRun.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,14 @@ extension Application {
6363
var progressConfig: ProgressConfig
6464
switch self.progressFlags.progress {
6565
case .none: progressConfig = try ProgressConfig(disableProgressUpdates: true)
66-
case .ansi:
66+
case .ansi, .plain, .color:
6767
progressConfig = try ProgressConfig(
6868
showTasks: true,
6969
showItems: true,
7070
ignoreSmallSize: true,
71-
totalTasks: 6
71+
totalTasks: 6,
72+
color: self.progressFlags.progress == .color,
73+
plain: self.progressFlags.progress == .plain
7274
)
7375
}
7476

Sources/ContainerCommands/Image/ImagePull.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ extension Application {
8080
var progressConfig: ProgressConfig
8181
switch self.progressFlags.progress {
8282
case .none: progressConfig = try ProgressConfig(disableProgressUpdates: true)
83-
case .ansi:
83+
case .ansi, .plain, .color:
8484
progressConfig = try ProgressConfig(
8585
showTasks: true,
8686
showItems: true,
8787
ignoreSmallSize: true,
88-
totalTasks: 2
88+
totalTasks: 2,
89+
color: self.progressFlags.progress == .color,
90+
plain: self.progressFlags.progress == .plain
8991
)
9092
}
9193

Sources/ContainerCommands/Image/ImagePush.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ extension Application {
7070
var progressConfig: ProgressConfig
7171
switch self.progressFlags.progress {
7272
case .none: progressConfig = try ProgressConfig(disableProgressUpdates: true)
73-
case .ansi:
73+
case .ansi, .color, .plain:
7474
progressConfig = try ProgressConfig(
7575
description: "Pushing image \(image.reference)",
7676
itemsName: "blobs",
7777
showItems: true,
7878
showSpeed: false,
79-
ignoreSmallSize: true
79+
ignoreSmallSize: true,
80+
color: self.progressFlags.progress == .color,
81+
plain: self.progressFlags.progress == .plain
8082
)
8183
}
8284

Sources/TerminalProgress/ProgressBar+Terminal.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,17 @@ extension ProgressBar {
6363
}
6464

6565
func displayText(_ text: String, terminating: String = "\r") {
66+
if config.plain {
67+
display(text + "\n")
68+
return
69+
}
6670
var text = text
71+
let visibleText = stripAnsi(text)
6772

6873
// Clears previously printed characters if the new string is shorter.
6974
printedWidth.withLock {
70-
text += String(repeating: " ", count: max($0 - text.count, 0))
71-
$0 = text.count
75+
text += String(repeating: " ", count: max($0 - visibleText.count, 0))
76+
$0 = visibleText.count
7277
}
7378
state.withLock {
7479
$0.output = text
@@ -77,7 +82,7 @@ extension ProgressBar {
7782
// Clears previously printed lines.
7883
var lines = ""
7984
if terminating.hasSuffix("\r") && terminalWidth > 0 {
80-
let lineCount = (text.count - 1) / terminalWidth
85+
let lineCount = (visibleText.count - 1) / terminalWidth
8186
for _ in 0..<lineCount {
8287
lines += EscapeSequence.moveUp
8388
}
@@ -86,4 +91,9 @@ extension ProgressBar {
8691
text = "\(text)\(terminating)\(lines)"
8792
display(text)
8893
}
94+
95+
private func stripAnsi(_ text: String) -> String {
96+
let pattern = #"\u001B\[[0-9;]*[A-Za-z]"#
97+
return text.replacingOccurrences(of: pattern, with: "", options: .regularExpression)
98+
}
8999
}

Sources/TerminalProgress/ProgressBar.swift

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ public final class ProgressBar: Sendable {
7272
$0.subDescription = ""
7373
$0.tasks += 1
7474
}
75+
if config.plain {
76+
render(force: true)
77+
}
7578
}
7679

7780
/// Updates the additional description of the progress bar.
@@ -80,6 +83,9 @@ public final class ProgressBar: Sendable {
8083
resetCurrentTask()
8184

8285
state.withLock { $0.subDescription = subDescription }
86+
if config.plain {
87+
render(force: true)
88+
}
8389
}
8490

8591
private func start(intervalSeconds: TimeInterval) async {
@@ -96,6 +102,9 @@ public final class ProgressBar: Sendable {
96102
/// Starts an animation of the progress bar.
97103
/// - Parameter intervalSeconds: The time interval between updates in seconds.
98104
public func start(intervalSeconds: TimeInterval = 0.04) {
105+
if config.plain {
106+
return
107+
}
99108
Task(priority: .utility) {
100109
await start(intervalSeconds: intervalSeconds)
101110
}
@@ -148,15 +157,15 @@ extension ProgressBar {
148157
if config.showSpinner && !config.showProgressBar {
149158
if !state.finished {
150159
let spinnerIcon = config.theme.getSpinnerIcon(state.iteration)
151-
components.append("\(spinnerIcon)")
160+
components.append(colorize("\(spinnerIcon)", Color.cyan))
152161
} else {
153-
components.append("\(config.theme.done)")
162+
components.append(colorize("\(config.theme.done)", Color.green))
154163
}
155164
}
156165

157166
if config.showTasks, let totalTasks = state.totalTasks {
158167
let tasks = min(state.tasks, totalTasks)
159-
components.append("[\(tasks)/\(totalTasks)]")
168+
components.append(colorize("[\(tasks)/\(totalTasks)]", Color.cyan))
160169
}
161170

162171
if config.showDescription && !state.description.isEmpty {
@@ -172,7 +181,7 @@ extension ProgressBar {
172181
let total = state.totalSize ?? Int64(state.totalItems ?? 0)
173182

174183
if config.showPercent && total > 0 && allowProgress {
175-
components.append("\(state.finished ? "100%" : state.percent)")
184+
components.append(colorize("\(state.finished ? "100%" : state.percent)", Color.green))
176185
}
177186

178187
if config.showProgressBar, total > 0, allowProgress {
@@ -181,7 +190,7 @@ extension ProgressBar {
181190
let barLength = state.finished ? remainingWidth : Int(Int64(remainingWidth) * value / total)
182191
let barPaddingLength = remainingWidth - barLength
183192
let bar = "\(String(repeating: config.theme.bar, count: barLength))\(String(repeating: " ", count: barPaddingLength))"
184-
components.append("|\(bar)|")
193+
components.append("|\(colorize(bar, Color.green))|")
185194
}
186195

187196
var additionalComponents = [String]()
@@ -246,13 +255,13 @@ extension ProgressBar {
246255

247256
if additionalComponents.count > 0 {
248257
let joinedAdditionalComponents = additionalComponents.joined(separator: ", ")
249-
components.append("(\(joinedAdditionalComponents))")
258+
components.append("(\(colorize(joinedAdditionalComponents, Color.cyan)))")
250259
}
251260

252261
if config.showTime {
253262
let timeDifferenceSeconds = secondsSinceStart()
254263
let formattedTime = timeDifferenceSeconds.formattedTime()
255-
components.append("[\(formattedTime)]")
264+
components.append("[\(colorize(formattedTime, Color.blue))]")
256265
}
257266

258267
return components.joined(separator: " ")
@@ -286,4 +295,16 @@ extension ProgressBar {
286295
}
287296
return "\(sizeNumber)/\(totalSizeNumber) \(totalSizeUnit)"
288297
}
298+
299+
private func colorize(_ text: String, _ color: String) -> String {
300+
guard config.color else { return text }
301+
return "\(color)\(text)\(Color.reset)"
302+
}
303+
304+
private enum Color {
305+
static let green = "\u{001B}[32m"
306+
static let cyan = "\u{001B}[36m"
307+
static let blue = "\u{001B}[34m"
308+
static let reset = "\u{001B}[0m"
309+
}
289310
}

Sources/TerminalProgress/ProgressConfig.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ public struct ProgressConfig: Sendable {
6565
public let clearOnFinish: Bool
6666
/// The flag indicating whether to update the progress bar.
6767
public let disableProgressUpdates: Bool
68+
/// The flag indicating whether to use colors.
69+
public let color: Bool
70+
/// The flag indicating whether to use plain output (no ANSI codes, one line per update).
71+
public let plain: Bool
6872
/// Creates a new instance of `ProgressConfig`.
6973
/// - Parameters:
7074
/// - terminal: The file handle for progress updates. The default value is `FileHandle.standardError`.
@@ -88,6 +92,8 @@ public struct ProgressConfig: Sendable {
8892
/// - theme: The theme of the progress bar. The default value is `nil`.
8993
/// - clearOnFinish: The flag indicating whether to clear the progress bar before resetting the cursor. The default is `true`.
9094
/// - disableProgressUpdates: The flag indicating whether to update the progress bar. The default is `false`.
95+
/// - color: A flag indicating whether to enable ANSI color output. The default value is `false`.
96+
/// - plain: A flag indicating whether to force plain output with no control sequences. The default value is `false`.
9197
public init(
9298
terminal: FileHandle = .standardError,
9399
description: String = "",
@@ -109,7 +115,9 @@ public struct ProgressConfig: Sendable {
109115
width: Int = 120,
110116
theme: ProgressTheme? = nil,
111117
clearOnFinish: Bool = true,
112-
disableProgressUpdates: Bool = false
118+
disableProgressUpdates: Bool = false,
119+
color: Bool = false,
120+
plain: Bool = false
113121
) throws {
114122
if let totalTasks {
115123
guard totalTasks > 0 else {
@@ -132,11 +140,11 @@ public struct ProgressConfig: Sendable {
132140
self.initialSubDescription = subDescription
133141
self.initialItemsName = itemsName
134142

135-
self.showSpinner = showSpinner
143+
self.showSpinner = plain ? false : showSpinner
136144
self.showTasks = showTasks
137145
self.showDescription = showDescription
138146
self.showPercent = showPercent
139-
self.showProgressBar = showProgressBar
147+
self.showProgressBar = plain ? false : showProgressBar
140148
self.showItems = showItems
141149
self.showSize = showSize
142150
self.showSpeed = showSpeed
@@ -151,6 +159,8 @@ public struct ProgressConfig: Sendable {
151159
self.theme = theme ?? DefaultProgressTheme()
152160
self.clearOnFinish = clearOnFinish
153161
self.disableProgressUpdates = disableProgressUpdates
162+
self.color = plain ? false : color
163+
self.plain = plain
154164
}
155165
}
156166

docs/command-reference.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ container run [<options>] <image> [<arguments> ...]
7979

8080
**Progress Options**
8181

82-
* `--progress <type>`: Progress type (format: none|ansi) (default: ansi)
82+
* `--progress <type>`: Progress type (format: none|ansi|plain|color) (default: ansi)
8383

8484
**Examples**
8585

@@ -444,7 +444,7 @@ container image pull [--debug] [--scheme <scheme>] [--progress <type>] [--arch <
444444
**Options**
445445

446446
* `--scheme <scheme>`: Scheme to use when connecting to the container registry. One of (http, https, auto) (default: auto)
447-
* `--progress <type>`: Progress type (format: none|ansi) (default: ansi)
447+
* `--progress <type>`: Progress type (format: none|ansi|plain|color) (default: ansi)
448448
* `-a, --arch <arch>`: Limit the pull to the specified architecture
449449
* `--os <os>`: Limit the pull to the specified OS
450450
* `--platform <platform>`: Limit the pull to the specified platform (format: os/arch[/variant], takes precedence over --os and --arch)
@@ -466,7 +466,7 @@ container image push [--scheme <scheme>] [--progress <type>] [--arch <arch>] [--
466466
**Options**
467467

468468
* `--scheme <scheme>`: Scheme to use when connecting to the container registry. One of (http, https, auto) (default: auto)
469-
* `--progress <type>`: Progress type (format: none|ansi) (default: ansi)
469+
* `--progress <type>`: Progress type (format: none|ansi|plain|color) (default: ansi)
470470
* `-a, --arch <arch>`: Limit the push to the specified architecture
471471
* `--os <os>`: Limit the push to the specified OS
472472
* `--platform <platform>`: Limit the push to the specified platform (format: os/arch[/variant], takes precedence over --os and --arch)

0 commit comments

Comments
 (0)