ProgressAnimationProtocol

/// A protocol to operate on terminal based progress animations.publicprotocolProgressAnimationProtocol{/// Update the animation with a new step./// - Parameters:/// - step: The index of the operation's current step./// - total: The total number of steps before the operation is complete./// - text: The description of the current step.funcupdate(step:Int,total:Int,text:String)/// Complete the animation./// - Parameters:/// - success: Defines if the operation the animation represents was succesful.funccomplete(success:Bool)/// Clear the animation.funcclear()}

Every animation is responsible to take care of any interpolation needed, and correctly display our progress (by means of progress percentage, or else).

To start an animation, we call update(step:total:text:), and we keep doing so until the job has been completed, which is when we let the animation know via complete(success:).

Lastly, the ProgressAnimationProtocol requires a clear function, which tells the animation to remove itself, allowing the terminal to proceed as if the animation was never shown.

Animations are just “print” statements: there’s no requirement on the order of the update/complete/clear calls, it’s entirely up to us.

DynamicProgressAnimation

Different terminals types support different control codes. Some animations are possible only if their control codes are available (e.g. to control the terminal cursor position and clear terminal lines).

In order to support all terminal types, TSCUtility defines DynamicProgressAnimation, which selects a different animation based on the terminal capability:

/// A progress animation that adapts to the provided output stream.publicclassDynamicProgressAnimation:ProgressAnimationProtocol{privateletanimation:ProgressAnimationProtocolpublicinit(stream:OutputByteStream,ttyTerminalAnimationFactory:(TerminalController)->ProgressAnimationProtocol,dumbTerminalAnimationFactory:()->ProgressAnimationProtocol,defaultAnimationFactory:()->ProgressAnimationProtocol){ifletterminal=TerminalController(stream:stream){animation=ttyTerminalAnimationFactory(terminal)}elseifletfileStream=streamas?LocalFileOutputByteStream,TerminalController.terminalType(fileStream)==.dumb{animation=dumbTerminalAnimationFactory()}else{animation=defaultAnimationFactory()}}publicfuncupdate(step:Int,total:Int,text:String){animation.update(step:step,total:total,text:text)}publicfunccomplete(success:Bool){animation.complete(success:success)}publicfuncclear(){animation.clear()}}

DynamicProgressAnimation takes in three animation factories and uses one of them depending on the given stream, which is an object conforming to the OutputByteStream protocol, used to manage different output destinations.

TSCUtility provides two DynamicProgressAnimation subclasses ready for us to use, NinjaProgressAnimation and PercentProgressAnimation.

NinjaProgressAnimation

/// A ninja-like progress animation that adapts to the provided output stream.publicfinalclassNinjaProgressAnimation:DynamicProgressAnimation{publicinit(stream:OutputByteStream){super.init(stream:stream,ttyTerminalAnimationFactory:{RedrawingNinjaProgressAnimation(terminal:$0)},dumbTerminalAnimationFactory:{SingleLinePercentProgressAnimation(stream:stream,header:nil)},defaultAnimationFactory:{MultiLineNinjaProgressAnimation(stream:stream)})}}

This is a middle ground between the previous two animations, where this new animation has the capability to return and create a new line. However no clear capabilities are used, which means that also in this case clear() calls are ignored.

If this animation looks familiar, it’s because it is! For example, we use it every time a package needs to resolve, download, and compile any package dependency:

You can see the RedrawingNinjaProgressAnimation in the line before the last one in the terminal.

Here’s the code to test the animation:

importDarwinimportTSCBasicimportTSCUtilityletanimation=NinjaProgressAnimation(stream:stdoutStream)foriin0..<100{letsecond:Double=1_000_000usleep(UInt32(second*0.05))animation.update(step:i,total:100,text:"Loading..")}animation.complete(success:true)// or animation.clear()

PercentProgressAnimation

/// A percent-based progress animation that adapts to the provided output stream.publicfinalclassPercentProgressAnimation:DynamicProgressAnimation{publicinit(stream:OutputByteStream,header:String){super.init(stream:stream,ttyTerminalAnimationFactory:{RedrawingLitProgressAnimation(terminal:$0,header:header)},dumbTerminalAnimationFactory:{SingleLinePercentProgressAnimation(stream:stream,header:header)},defaultAnimationFactory:{MultiLinePercentProgressAnimation(stream:stream,header:header)})}}

Conclusions

In this article we’ve seen how progress animations are defined in TSCUtility, how different terminals require different animations, and how TSCUtility standardizes them by defining three types of animations:

Redrawing when the animation can redraw itself in place

SingleLine when the whole progress is “printed” in one line

MultiLine when we print the progress state in a new line at every update.

If you need to show progress to in your scripts, you now know where to look!