-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cabc528
commit 086bfc2
Showing
5 changed files
with
217 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import Foundation | ||
import Rainbow | ||
|
||
|
||
class CLIProgressBar { | ||
|
||
let message: String | ||
let successMessage: String? | ||
let errorMessage: String? | ||
let total: Int | ||
let action: (@escaping (String) -> Void) async throws -> Void | ||
let theme: Theme | ||
let terminal: Terminaling | ||
let renderer: Rendering | ||
let standardPipelines: StandardPipelines | ||
var progressBar: ProgressBar | ||
|
||
init( | ||
message: String, | ||
successMessage: String?, | ||
errorMessage: String?, | ||
total: Int, | ||
action: @escaping (@escaping (String) -> Void) async throws -> Void, | ||
theme: Theme, | ||
terminal: Terminaling, | ||
renderer: Rendering, | ||
standardPipelines: StandardPipelines, | ||
progressBar: ProgressBar = DefaultProgressBar() | ||
) { | ||
self.message = message | ||
self.successMessage = successMessage | ||
self.errorMessage = errorMessage | ||
self.total = total | ||
self.action = action | ||
self.theme = theme | ||
self.terminal = terminal | ||
self.renderer = renderer | ||
self.standardPipelines = standardPipelines | ||
self.progressBar = progressBar | ||
} | ||
|
||
func run() async throws { | ||
if terminal.isInteractive { | ||
try await runInteractive() | ||
} else { | ||
try await runNonInteractive() | ||
} | ||
} | ||
|
||
func runInteractive() async throws { | ||
|
||
var bar: String = "" | ||
var progressPercentage = 0 | ||
var lastMessage = message | ||
|
||
progressBar.startProgress(total: total, interval: 0.05) { progressBarState, percentage in | ||
bar = progressBarState | ||
progressPercentage = percentage | ||
self.render(lastMessage, bar, progressPercentage) | ||
} | ||
|
||
var _error: Error? | ||
do { | ||
self.render(lastMessage, bar, progressPercentage) | ||
try await action { progressMessage in | ||
lastMessage = progressMessage | ||
self.render(lastMessage, bar, progressPercentage) | ||
} | ||
} catch { | ||
_error = error | ||
} | ||
|
||
|
||
if _error != nil { | ||
renderer.render( | ||
"\("⨯".hexIfColoredTerminal(theme.danger, terminal)) \(errorMessage ?? message)", | ||
standardPipeline: standardPipelines.error | ||
) | ||
} else { | ||
renderer.render( | ||
CLIProgressBar | ||
.completionMessage(lastMessage, bar, progressPercentage, theme: theme, terminal: terminal), | ||
standardPipeline: standardPipelines.output | ||
) | ||
} | ||
|
||
if let _error { | ||
throw _error | ||
} | ||
} | ||
// TODO: Implement runNonInteractive logic | ||
func runNonInteractive() async throws { | ||
|
||
} | ||
|
||
static func completionMessage(_ message: String, _ bar: String, _ percentage: Int, theme: Theme, terminal: Terminaling) -> String { | ||
"\(message) \(bar.hexIfColoredTerminal(theme.success, terminal)) \(percentage)% | Completed" | ||
} | ||
|
||
private func render(_ message: String, _ bar: String, _ percentage: Int) { | ||
renderer.render( | ||
"\(message) \(bar.hexIfColoredTerminal(theme.primary, terminal)) \(percentage)% |", | ||
standardPipeline: standardPipelines.output | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import Foundation | ||
|
||
protocol ProgressBar { | ||
func startProgress(total: Int, interval: TimeInterval?, block: @escaping (String, Int) -> Void) | ||
func stop() | ||
} | ||
class DefaultProgressBar: ProgressBar { | ||
private static let complete = "█" | ||
private static let incomplete = "▒" | ||
private static let width = 30 | ||
|
||
private var isLoading = true | ||
private var timer: Timer? | ||
private var completed = 0 | ||
private var progressPercent = 0 | ||
|
||
func startProgress(total: Int, interval: TimeInterval? , block: @escaping (String, Int) -> Void) { | ||
isLoading = true | ||
|
||
DispatchQueue.global(qos: .userInitiated).async { | ||
let runLoop = RunLoop.current | ||
var index = 1 | ||
|
||
// Schedule the timer in the current run loop | ||
self.timer = Timer.scheduledTimer(withTimeInterval: interval ?? 0.05, repeats: true) { _ in | ||
if index <= total { | ||
self.update(total, index) | ||
let completedBar = String(repeating: DefaultProgressBar.complete, count: self.completed) | ||
let incompleteBar = String(repeating: DefaultProgressBar.incomplete, count: DefaultProgressBar.width - self.completed) | ||
block(completedBar+incompleteBar, self.progressPercent) | ||
index += 1 | ||
} else { | ||
self.timer?.invalidate() | ||
} | ||
} | ||
|
||
// Start the run loop to allow the timer to fire | ||
while self.isLoading, runLoop.run(mode: .default, before: .distantFuture) {} | ||
} | ||
} | ||
|
||
private func update(_ total: Int, _ index: Int) { | ||
let percentage = Double(index) / Double(total) | ||
completed = Int(percentage * Double(DefaultProgressBar.width)) | ||
progressPercent = Int(percentage * 100) | ||
} | ||
|
||
func stop() { | ||
isLoading = false | ||
timer?.invalidate() | ||
timer = nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import ArgumentParser | ||
import Foundation | ||
import Noora | ||
|
||
struct ProgressBarCommand: AsyncParsableCommand { | ||
static let configuration = CommandConfiguration( | ||
commandName: "progress-bar", | ||
abstract: "A component to shows a progress bar" | ||
) | ||
func run() async throws { | ||
try await Noora().progressBar( | ||
message: "Loading", | ||
successMessage: "Manifests loaded", | ||
errorMessage: "Failed to load manifests", | ||
total: 100 | ||
) { _ in | ||
try await Task.sleep(nanoseconds: 5_000_000_000) | ||
} | ||
try await Noora().progressBar( | ||
message: "Loading", | ||
successMessage: "Manifests loaded", | ||
errorMessage: "Failed to load manifests", | ||
total: 200 | ||
) { _ in | ||
try await Task.sleep(nanoseconds: 10_000_000_000) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters