Convert standard input into pretty-printed tables. Inspired by https://github.com/joepvd/table. Works in Tcl 8.5+ and [Jim Tcl]. ** Use example ** ====== $ ps | jimsh ./tabulate.tcl ┌─────┬─────┬────────┬─────┐ │ PID │ TTY │ TIME │ CMD │ ├─────┼─────┼────────┼─────┤ │20583│pts/3│00:00:01│ zsh │ ├─────┼─────┼────────┼─────┤ │23301│pts/3│00:00:00│ ne │ ├─────┼─────┼────────┼─────┤ │28007│pts/3│00:00:00│ ps │ ├─────┼─────┼────────┼─────┤ │28008│pts/3│00:00:00│jimsh│ └─────┴─────┴────────┴─────┘ ====== ** Code ** ====== #! /usr/bin/env tclsh # Tabulate v0.2.0 -- turn standard input into a table. # Copyright (C) 2015 Danyil Bohdan # License: MIT namespace eval ::tabulate { variable defaultStyle { top { left ┌ padding ─ separator ┬ right ┐ } separator { left ├ padding ─ separator ┼ right ┤ } row { left │ padding { } separator │ right │ } bottom { left └ padding ─ separator ┴ right ┘ } } } # Return a value from dictionary like [dict get] would if it is there. # Otherwise return the default value. proc ::tabulate::dict-get-default {dictionary default args} { if {[dict exists $dictionary {*}$args]} { dict get $dictionary {*}$args } else { return $default } } # Format a list as a table row. Does *not* append a newline after the row. proc ::tabulate::formatRow {row columnWidths substyle} { set result {} append result [dict get $substyle left] set fieldCount [expr { [llength $columnWidths] / 2 }] for {set i 0} {$i < $fieldCount} {incr i} { set field [lindex $row $i] set padding [expr { [dict get $columnWidths $i] - [string length $field] }] set rightPadding [expr { $padding / 2 }] set leftPadding [expr { $padding - $rightPadding }] append result [string repeat [dict get $substyle padding] $leftPadding] append result $field append result [string repeat [dict get $substyle padding] $rightPadding] if {$i < $fieldCount - 1} { append result [dict get $substyle separator] } } append result [dict get $substyle right] return $result } # Convert a list of lists into a string representing a table in pseudographics. proc ::tabulate::tabulate args { set data [dict get $args -data] set style [dict-get-default $args $::tabulate::defaultStyle -style] # Find out the maximum width of each column. set columnWidths {} ;# Dictionary. foreach row $data { for {set i 0} {$i < [llength $row]} {incr i} { set field [lindex $row $i] set currentLength [string length $field] set width [dict-get-default $columnWidths 0 $i] if {$currentLength > $width} { dict set columnWidths $i $currentLength } } } # A dummy row for formatting the table's decorative elements with # [formatRow]. set emptyRow {} for {set i 0} {$i < ([llength $columnWidths] / 2)} {incr i} { lappend emptyRow {} } set result {} set rowCount [llength $data] # Top of the table. lappend result [formatRow $emptyRow $columnWidths [dict get $style top]] # For each row... for {set i 0} {$i < $rowCount} {incr i} { set row [lindex $data $i] # Row. lappend result [formatRow $row $columnWidths [dict get $style row]] # Separator. if {$i < $rowCount - 1} { lappend result [formatRow $emptyRow $columnWidths \ [dict get $style separator]] } } # Bottom of the table. lappend result [::tabulate::formatRow $emptyRow $columnWidths \ [dict get $style bottom]] return [join $result \n] } # Read the input, process the command line options and output the result. proc ::tabulate::main {argv0 argv} { set data [lrange [split [read stdin] \n] 0 end-1] # Input field separator. If none is given treat each line of input as a Tcl # list. set FS [dict-get-default $argv {} -FS] if {$FS ne {}} { set updateData {} foreach line $data { lappend updateData [split $line $FS] } set data $updateData } puts [::tabulate::tabulate -data $data] } # If this is the main script... if {[info exists argv0] && ([file tail [info script]] eq [file tail $argv0])} { ::tabulate::main $argv0 $argv } ====== ** See also ** <> Application | Jim Package | Text Screen