๐Ÿ‘”Logger

๐Ÿ’พ ็จ‹ๅผ๏ผš replit โฌ†๏ธ ้œ€่ฆ๏ผš str.pad(), collection.columnWidths

// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// 2022.01.27 * (v.1) + line, spaces, prefixDoubleSlash, log, underline, doubleline, debug
// 2022.01.28 / line()       replace "-" by "โ”€" (longer dash), 
//            r line()       renmaed to "_line()"
//            / log()        `dot`: Bool -> String?
//            + line()       draw line 
//            + topline()    line on top of message
//            + error()      error message
// 2022.01.29 + codeBlock()  `///` prefixed code block
//            + table()      draw a table for 2D data
// 2022.01.30 / table()      + param options: TableOptions
//            / table()      + return value (Bool), + @discardableResult
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// #todo: โ–  โ–ก โ–จ 
// - table()     + โ–  dot  โ–ก vertical borders  โ–ก vertical grid lines
// - codeBlock() + โ–ก padding before code  โ–ก comments after code

// import Extensions            // for String + .pad()

// โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
// โ”‚    Logger    โ”‚
// โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

public struct Logger {
    public static var prefixDoubleSlash = false
}

// helper
extension Logger {
    /// `Logger.line(3) == "-" * 3 == "---"` 
    static func _line  (_ n: Int) -> String { return "โ”€" * n }
    /// `Logger.spaces(3) == " " * 3 == "   "`
    static func spaces(_ n: Int) -> String { return " " * n }
}

extension Logger {
    public struct TableOptions: OptionSet {
        
        public let rawValue: Int
        
        public init(rawValue: Int) {
            self.rawValue = rawValue
        }
        
        // โญ๏ธ unit sets (containing only one element)
        public static let dot    = TableOptions(rawValue: 1 << 0)
    }
}

extension Logger {
    
    /// `Logger.log(msg)`
    public static func log(
        _ message: String = "", 
        padding n: Int = 0,
        dot: String? = nil
    ) {
        let prefix = prefixDoubleSlash ? "// " : ""
        let padding = spaces(n)
        let dotted = (dot == nil) ? "" : "\(dot!) "
        print(prefix + padding + dotted + message)
    }
    
    /// `Logger.error(msg)`
    public static func error(
        _ message: String = "", 
        padding n: Int = 0
    ) {
        Logger.log(message, padding: n, dot: "โ›”")
    }
    
    /// `Logger.line(n)`
    public static func line(_ n: Int, padding p: Int = 0) {
        log(Logger._line(n), padding: p)
    }
    
    /// `Logger.box(title)`
    public static func box(_ title: String, padding: Int = 4) {
        let w = title.count + padding * 2
        let line = "โ”€" * w
        let spaces = " " * padding
        
        Logger.log("โ”Œ" + line + "โ”")
        Logger.log("โ”‚" + spaces + title + spaces + "โ”‚")
        Logger.log("โ””" + line + "โ”˜")
    }
    
    /// `Logger._drawline(msg)`
    static func _drawline(
        _ message : String, 
        length   n: Int? = nil,         // line length
        padding  p: Int  = 0,           // padding on both sides of message
        topline   : Bool = false,       // draw top line
        bottomline: Bool = true         // draw bottom line
    ) {
        let defaultLength = message.count + p * 2
        let line = Logger._line(n ?? defaultLength)
        let padding = spaces(p)
        
        if topline { log(line) }
        log(padding + message + padding)
        if bottomline { log(line) }
    }
    
    /// `Logger.underline(msg)`
    public static func underline(_ message: String, length n: Int? = nil, padding p: Int = 0) {
        _drawline(message, length: n, padding: p)
    }
    
    /// `Logger.doubleline(msg)`
    public static func doubleline(
        _ message: String, 
        length  n: Int? = nil,         // line length
        padding p: Int  = 4,           // padding on both sides of message
        newline  : Bool = true         // newline before message
    ) {
        if newline { log() }           // new line
        _drawline(message, length: n, padding: p, topline: true)
    }
    
    /// `Logger.topline(msg)`
    public static func topline(
        _ message: String, 
        length  n: Int? = nil,         // line length
        padding p: Int  = 0            // padding on both sides of message
    ) {
        _drawline(message, length: n, padding: p, topline: true, bottomline: false)
    }
    
    /// `Logger.debug(msg)`
    public static func debug(
        _ message: String  = "",
        file     : String  = #file,         // โญ file name
        line     : Int     = #line,         // โญ line number
        function : String  = #function,     // โญ function name
        note     : String? = nil            // additional info
    ) {
        let msgStr = "๐Ÿž \(message)"
        let funcStr = "func: '\(function)'"
        let lineStr = "line: \(line)"
        let fileStr = "file: '\(file)'"
        let debugInfo = "   [\(funcStr), \(lineStr), \(fileStr)]"
        
        log()
        log(msgStr)
        log(debugInfo)
        if let note = note { log(note) }
    }
    
    /// print a `///` + ```-delimited code block to console.
    /// #todo: + 1. padding before code, 2. comments after code
    public static func codeBlock(_ code: [String]) {
        print("/// ```")
        code.forEach { print("/// " + $0) }
        print("/// ```")
    }
    
    /// log a table of data
    @discardableResult
    public static func table(
        _ data: [[String]], 
        padding p: Int = 2, 
        alignments: [Int:String.PadAlign] = [:],
        options: TableOptions = []
    ) -> Bool {
        // ๐ŸŒ€ Collection+
        guard let w = data.columnWidths else {
            return false        // Logger.table() failed
        }
        
        // all widths + spacing
        var lineLength = w.reduce(0, +) + (w.count) * 2 * p
        if options.contains(.dot) { lineLength += 1 + p }        // + (padding) + (dot) on left-side of 1st cell
        
        let hr = "โ”€" * lineLength        // horizontal line
        let padding = " " * p
        
        // table top border
        Logger.log(hr)
        
        // for each row in the data
        for i in data.indices {
            var line = ""
            let row = data[i]
            // dot before each line
            let dotted = options.contains(.dot) 
                ? padding + (i == 0 ? " " : "๐Ÿ”ธ")    // header without dot
                : ""
            // for each string in the row
            for j in row.indices {
                let str = row[j]        // string in cell
                let width = w[j]        // cell width
                let align = i == 0 ? .center : alignments[j+1] ?? .left    // header is always centered
                // ๐ŸŒ€ String+
                let cell = str.pad(width: width, align: align)             // padded cell
                // padding on both sides of each cell
                line += padding + cell + padding
            }
            line = dotted + line
            Logger.log(line)
            if i == 0 { Logger.log(hr) }    // header line
        }
        
        // table bottom border
        Logger.log(hr)

        return true         // Logger.table() successful
    }
}

History

  1. (2022.01.25) * (v.1) as a global function.

  2. (2022.01.28) + topline(), error()

Last updated