-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add progress step component #139
base: main
Are you sure you want to change the base?
Changes from all commits
4cf96f3
eb7cf8f
9f908ec
6a72b75
81f794f
1cd3105
86958f1
1e10137
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,153 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import Foundation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import Rainbow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class ProgressStep { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be a |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// MARK: - Attributes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let message: String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let successMessage: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let errorMessage: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let showSpinner: Bool | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let action: (@escaping (String) -> Void) async throws -> Void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let theme: Theme | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let terminal: Terminaling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let renderer: Rendering | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let standardPipelines: StandardPipelines | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var spinner: Spinning | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
init( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
message: String, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
successMessage: String?, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
errorMessage: String?, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
showSpinner: Bool, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
action: @escaping (@escaping (String) -> Void) async throws -> Void, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
theme: Theme, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+19
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd keep the |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
terminal: Terminaling, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
renderer: Rendering, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
standardPipelines: StandardPipelines, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
spinner: Spinning = Spinner() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.message = message | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.successMessage = successMessage | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.errorMessage = errorMessage | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.showSpinner = showSpinner | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.action = action | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.theme = theme | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.terminal = terminal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.renderer = renderer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.standardPipelines = standardPipelines | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.spinner = spinner | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func run() async throws { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if terminal.isInteractive { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try await runInteractive() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try await runNonInteractive() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func runNonInteractive() async throws { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// ℹ︎ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let start = DispatchTime.now() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// swiftlint:disable:next identifier_name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var _error: Error? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
do { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
standardPipelines.output.write(content: "\("ℹ︎".hexIfColoredTerminal(theme.primary, terminal)) \(message)\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try await action { progressMessage in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.standardPipelines.output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.write(content: " \(progressMessage.hexIfColoredTerminal(self.theme.muted, self.terminal))\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_error = error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let elapsedTime = Double(DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let timeString = "[\(String(format: "%.1f", elapsedTime))s]".hexIfColoredTerminal(theme.muted, terminal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if _error != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
standardPipelines.error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.write( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
content: " \("⨯".hexIfColoredTerminal(theme.danger, terminal)) \((errorMessage ?? message).hexIfColoredTerminal(theme.muted, terminal)) \(timeString)\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+72
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if static |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let message = ProgressStep | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.completionMessage(successMessage ?? message, timeString: timeString, theme: theme, terminal: terminal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
standardPipelines.output.write(content: " \(message)\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// swiftlint:disable:next identifier_name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let _error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw _error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+63
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd extract the following two lines into a helped method/closure:
That way, we can save ourselves some of the dancing with
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same goes for |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func runInteractive() async throws { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let start = DispatchTime.now() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defer { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if showSpinner { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
spinner.stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var spinnerIcon: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var lastMessage = message | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if showSpinner { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
spinner.spin { icon in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
spinnerIcon = icon | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.render(message: lastMessage, icon: spinnerIcon ?? "ℹ︎") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// swiftlint:disable:next identifier_name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var _error: Error? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
do { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
render(message: lastMessage, icon: spinnerIcon ?? "ℹ︎") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try await action { progressMessage in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lastMessage = progressMessage | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.render(message: lastMessage, icon: spinnerIcon ?? "ℹ︎") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_error = error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let elapsedTime = Double(DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let timeString = "[\(String(format: "%.1f", elapsedTime))s]".hexIfColoredTerminal(theme.muted, terminal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if _error != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
renderer.render( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"\("⨯".hexIfColoredTerminal(theme.danger, terminal)) \(errorMessage ?? message) \(timeString)", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
standardPipeline: standardPipelines.error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
renderer.render( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ProgressStep | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.completionMessage(successMessage ?? message, timeString: timeString, theme: theme, terminal: terminal), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
standardPipeline: standardPipelines.output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// swiftlint:disable:next identifier_name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let _error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw _error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// MARK: - Private | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
static func completionMessage(_ message: String, timeString: String? = nil, theme: Theme, terminal: Terminaling) -> String { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"\("✔︎".hexIfColoredTerminal(theme.success, terminal)) \(message)\(timeString != nil ? " \(timeString!)" : "")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private func render(message: String, icon: String) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
renderer.render( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"\(icon.hexIfColoredTerminal(theme.primary, terminal)) \(message)", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
standardPipeline: standardPipelines.output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -200,4 +200,25 @@ public struct Noora: Noorable { | |
theme: theme | ||
).run() | ||
} | ||
|
||
public func progressStep( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is not part of the |
||
message: String, | ||
successMessage: String? = nil, | ||
errorMessage: String? = nil, | ||
showSpinner: Bool = true, | ||
action: @escaping ((String) -> Void) async throws -> Void | ||
) async throws { | ||
let progressStep = ProgressStep( | ||
message: message, | ||
successMessage: successMessage, | ||
errorMessage: errorMessage, | ||
showSpinner: showSpinner, | ||
action: action, | ||
theme: theme, | ||
terminal: terminal, | ||
renderer: Renderer(), | ||
standardPipelines: StandardPipelines() | ||
) | ||
try await progressStep.run() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import ArgumentParser | ||
import Foundation | ||
import Noora | ||
|
||
struct ProgressStepCommand: AsyncParsableCommand { | ||
static let configuration = CommandConfiguration( | ||
commandName: "progress-step", | ||
abstract: "A component to shows a progress step" | ||
) | ||
|
||
func run() async throws { | ||
try await Noora().progressStep( | ||
message: "Loading manifests", | ||
successMessage: "Manifests loaded", | ||
errorMessage: "Failed to load manifests" | ||
) { _ in | ||
try await Task.sleep(nanoseconds: 2_000_000_000) | ||
} | ||
try await Noora().progressStep( | ||
message: "Processing the graph", | ||
successMessage: "Project graph processed", | ||
errorMessage: "Failed to process the project graph" | ||
) { _ in | ||
try await Task.sleep(nanoseconds: 2_000_000_000) | ||
} | ||
try await Noora().progressStep( | ||
message: "Generating Xcode projects and workspace", | ||
successMessage: "Xcode projects and workspace generated", | ||
errorMessage: "Failed to generate Xcode workspace and projects" | ||
) { _ in | ||
try await Task.sleep(nanoseconds: 2_000_000_000) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be kept true – otherwise, it's easy to use the wrong message when there's just one commit