Created
July 15, 2016 17:37
-
-
Save aciidgh/75de71db0f2580a08b4f40ee82a67589 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
protocol DumbTerminalProtocol { | |
func write(_ string: String) | |
func endLine() | |
func flush() | |
} | |
protocol RichTerminalProtocol: DumbTerminalProtocol { | |
var width: Int { get } | |
func clearLine() | |
func moveCursor(y: Int) | |
func write(_ string: String, inColor color: TermColor) | |
func wrap(_ string: String, inColor color: TermColor) -> String | |
} | |
final class DumbTerminal<Target: OutputStream>: DumbTerminalProtocol { | |
private var stream: UnsafeMutablePointer<Target> | |
public init(stream: UnsafeMutablePointer<Target>) { | |
self.stream = stream | |
} | |
func write(_ string: String) { | |
writeToStream(string) | |
flush() | |
} | |
func endLine() { | |
writeToStream("\n") | |
flush() | |
} | |
func flush() { | |
if case let fileStream as OutputFileStreamProtocol = stream.pointee { | |
fflush(fileStream.file) | |
} | |
} | |
private func writeToStream(_ string: String) { | |
print(string, terminator: "", to: &stream.pointee) | |
} | |
} | |
final class RichTerminal<Target: OutputStream>: RichTerminalProtocol { | |
private var stream: UnsafeMutablePointer<Target> | |
public let width: Int | |
public init?(stream: UnsafeMutablePointer<Target>) { | |
// Make sure this is a file stream and tty. | |
guard case let stdStream as OutputFileStreamProtocol = stream.pointee, | |
(isatty(stdStream.fd) == 0) else { | |
return nil | |
} | |
width = terminalWidth() ?? 80 // Assume default if we are not able to determine. | |
self.stream = stream | |
} | |
func write(_ string: String) { | |
writeToStream(string) | |
flush() | |
} | |
/// Flushes the stream if writing to a filestream. | |
func flush() { | |
if case let fileStream as OutputFileStreamProtocol = stream.pointee { | |
fflush(fileStream.file) | |
} | |
} | |
private func writeToStream(_ string: String) { | |
print(string, terminator: "", to: &stream.pointee) | |
} | |
/// Code to clear the line on a tty. | |
private let clearLineString = "\u{001B}[2K" | |
/// Code to end any currently active wrapping. | |
private let resetString = "\u{001B}[0m" | |
/// Clears the current line and moves the cursor to beginning of the line.. | |
func clearLine() { | |
writeToStream(clearLineString + "\r") | |
} | |
/// Moves the cursor y columns up. | |
func moveCursor(y: Int) { | |
writeToStream("\u{001B}[\(y)A") | |
} | |
/// Writes a string to the stream. | |
func write(_ string: String, inColor color: TermColor = .noColor) { | |
writeToStream(wrap(string, inColor: color)) | |
flush() | |
} | |
/// Inserts a new line character into the stream. | |
func endLine() { | |
write("\n") | |
flush() | |
} | |
/// Wraps the string into the color mentioned. If terminal is dumb no color code will be inserted. | |
func wrap(_ string: String, inColor color: TermColor) -> String { | |
guard !string.isEmpty || color == .noColor else { | |
return string | |
} | |
return color.string + string + resetString | |
} | |
} | |
/// A protocol to operate on terminal based progress bars. | |
public protocol ProgressBarProtocol { | |
func update(percent: Int, text: String) | |
func complete() | |
} | |
// Simple ProgressBar which shows the update text in new lines. | |
public final class SimpleProgressBar: ProgressBarProtocol { | |
private let term: DumbTerminalProtocol | |
private let header: String | |
private var isClear: Bool | |
init(term: DumbTerminalProtocol, header: String) { | |
self.term = term | |
self.header = header | |
self.isClear = true | |
} | |
public func update(percent: Int, text: String) { | |
if isClear { | |
term.write(header) | |
term.endLine() | |
isClear = false | |
} | |
term.write("\(percent)%: " + text) | |
term.endLine() | |
} | |
public func complete() { | |
} | |
} | |
/////// Clients /////////// | |
/// Three line progress bar with header, redraws on each update. | |
final class ProgressBar: ProgressBarProtocol { | |
private let term: RichTerminalProtocol | |
private let header: String | |
private var isClear: Bool // true if haven't drawn anything yet. | |
init(term: RichTerminalProtocol, header: String) { | |
self.term = term | |
self.header = header | |
self.isClear = true | |
} | |
func update(percent: Int, text: String) { | |
if isClear { | |
term.write(header, inColor: .red) | |
term.endLine() | |
isClear = false | |
} | |
term.clearLine() | |
let percentString = percent < 10 ? "0\(percent)" : "\(percent)" | |
let prefix = "\(percentString)% [" | |
term.write(prefix, inColor: .green) | |
// FIXME: Fix the calculation here. | |
let barWidth = term.width - prefix.utf8.count - 20 | |
let n = Int(Double(barWidth) * Double(percent)/100.0) | |
term.write("=".repeating(n: n) + "-".repeating(n: barWidth - n)) | |
term.write("]", inColor: .green) | |
term.endLine() | |
term.clearLine() | |
term.write(text) | |
term.moveCursor(y: 1) | |
} | |
func complete() { | |
term.endLine() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment