@ -36,8 +36,8 @@ const (
)
)
var (
var (
decimal = regexp . MustCompile ( ` ^-*\d*\.?\d* $ ` )
decimal = regexp . MustCompile ( ` ^-?(?:\d { 1,3}(?:,\d { 3})*|\d+)(?:\.\d+)? $ ` )
percent = regexp . MustCompile ( ` ^-*\d* \.?\d*$%$ ` )
percent = regexp . MustCompile ( ` ^-?\d+ \.?\d*$%$ ` )
)
)
type Border struct {
type Border struct {
@ -53,10 +53,13 @@ type Table struct {
lines [ ] [ ] [ ] string
lines [ ] [ ] [ ] string
cs map [ int ] int
cs map [ int ] int
rs map [ int ] int
rs map [ int ] int
headers [ ] string
headers [ ] [ ] string
footers [ ] string
footers [ ] [ ] string
caption bool
captionText string
autoFmt bool
autoFmt bool
autoWrap bool
autoWrap bool
reflowText bool
mW int
mW int
pCenter string
pCenter string
pRow string
pRow string
@ -72,6 +75,10 @@ type Table struct {
hdrLine bool
hdrLine bool
borders Border
borders Border
colSize int
colSize int
headerParams [ ] string
columnsParams [ ] string
footerParams [ ] string
columnsAlign [ ] int
}
}
// Start New Table
// Start New Table
@ -83,10 +90,13 @@ func NewWriter(writer io.Writer) *Table {
lines : [ ] [ ] [ ] string { } ,
lines : [ ] [ ] [ ] string { } ,
cs : make ( map [ int ] int ) ,
cs : make ( map [ int ] int ) ,
rs : make ( map [ int ] int ) ,
rs : make ( map [ int ] int ) ,
headers : [ ] string { } ,
headers : [ ] [ ] string { } ,
footers : [ ] string { } ,
footers : [ ] [ ] string { } ,
caption : false ,
captionText : "Table caption." ,
autoFmt : true ,
autoFmt : true ,
autoWrap : true ,
autoWrap : true ,
reflowText : true ,
mW : MAX_ROW_WIDTH ,
mW : MAX_ROW_WIDTH ,
pCenter : CENTER ,
pCenter : CENTER ,
pRow : ROW ,
pRow : ROW ,
@ -100,12 +110,16 @@ func NewWriter(writer io.Writer) *Table {
rowLine : false ,
rowLine : false ,
hdrLine : true ,
hdrLine : true ,
borders : Border { Left : true , Right : true , Bottom : true , Top : true } ,
borders : Border { Left : true , Right : true , Bottom : true , Top : true } ,
colSize : - 1 }
colSize : - 1 ,
headerParams : [ ] string { } ,
columnsParams : [ ] string { } ,
footerParams : [ ] string { } ,
columnsAlign : [ ] int { } }
return t
return t
}
}
// Render table output
// Render table output
func ( t Table ) Render ( ) {
func ( t * Table ) Render ( ) {
if t . borders . Top {
if t . borders . Top {
t . printLine ( true )
t . printLine ( true )
}
}
@ -115,20 +129,27 @@ func (t Table) Render() {
} else {
} else {
t . printRows ( )
t . printRows ( )
}
}
if ! t . rowLine && t . borders . Bottom {
if ! t . rowLine && t . borders . Bottom {
t . printLine ( true )
t . printLine ( true )
}
}
t . printFooter ( )
t . printFooter ( )
if t . caption {
t . printCaption ( )
}
}
}
const (
headerRowIdx = - 1
footerRowIdx = - 2
)
// Set table header
// Set table header
func ( t * Table ) SetHeader ( keys [ ] string ) {
func ( t * Table ) SetHeader ( keys [ ] string ) {
t . colSize = len ( keys )
t . colSize = len ( keys )
for i , v := range keys {
for i , v := range keys {
t . parseDimension ( v , i , - 1 )
lines := t . parseDimension ( v , i , headerRowIdx )
t . headers = append ( t . headers , v )
t . headers = append ( t . headers , lines )
}
}
}
}
@ -136,8 +157,16 @@ func (t *Table) SetHeader(keys []string) {
func ( t * Table ) SetFooter ( keys [ ] string ) {
func ( t * Table ) SetFooter ( keys [ ] string ) {
//t.colSize = len(keys)
//t.colSize = len(keys)
for i , v := range keys {
for i , v := range keys {
t . parseDimension ( v , i , - 1 )
lines := t . parseDimension ( v , i , footerRowIdx )
t . footers = append ( t . footers , v )
t . footers = append ( t . footers , lines )
}
}
// Set table Caption
func ( t * Table ) SetCaption ( caption bool , captionText ... string ) {
t . caption = caption
if len ( captionText ) == 1 {
t . captionText = captionText [ 0 ]
}
}
}
}
@ -151,11 +180,21 @@ func (t *Table) SetAutoWrapText(auto bool) {
t . autoWrap = auto
t . autoWrap = auto
}
}
// Turn automatic reflowing of multiline text when rewrapping. Default is on (true).
func ( t * Table ) SetReflowDuringAutoWrap ( auto bool ) {
t . reflowText = auto
}
// Set the Default column width
// Set the Default column width
func ( t * Table ) SetColWidth ( width int ) {
func ( t * Table ) SetColWidth ( width int ) {
t . mW = width
t . mW = width
}
}
// Set the minimal width for a column
func ( t * Table ) SetColMinWidth ( column int , width int ) {
t . cs [ column ] = width
}
// Set the Column Separator
// Set the Column Separator
func ( t * Table ) SetColumnSeparator ( sep string ) {
func ( t * Table ) SetColumnSeparator ( sep string ) {
t . pColumn = sep
t . pColumn = sep
@ -186,6 +225,22 @@ func (t *Table) SetAlignment(align int) {
t . align = align
t . align = align
}
}
func ( t * Table ) SetColumnAlignment ( keys [ ] int ) {
for _ , v := range keys {
switch v {
case ALIGN_CENTER :
break
case ALIGN_LEFT :
break
case ALIGN_RIGHT :
break
default :
v = ALIGN_DEFAULT
}
t . columnsAlign = append ( t . columnsAlign , v )
}
}
// Set New Line
// Set New Line
func ( t * Table ) SetNewLine ( nl string ) {
func ( t * Table ) SetNewLine ( nl string ) {
t . newLine = nl
t . newLine = nl
@ -249,16 +304,44 @@ func (t *Table) AppendBulk(rows [][]string) {
}
}
}
}
// NumLines to get the number of lines
func ( t * Table ) NumLines ( ) int {
return len ( t . lines )
}
// Clear rows
func ( t * Table ) ClearRows ( ) {
t . lines = [ ] [ ] [ ] string { }
}
// Clear footer
func ( t * Table ) ClearFooter ( ) {
t . footers = [ ] [ ] string { }
}
// Center based on position and border.
func ( t * Table ) center ( i int ) string {
if i == - 1 && ! t . borders . Left {
return t . pRow
}
if i == len ( t . cs ) - 1 && ! t . borders . Right {
return t . pRow
}
return t . pCenter
}
// Print line based on row width
// Print line based on row width
func ( t Table ) printLine ( nl bool ) {
func ( t * Table ) printLine ( nl bool ) {
fmt . Fprint ( t . out , t . pCenter )
fmt . Fprint ( t . out , t . center ( - 1 ) )
for i := 0 ; i < len ( t . cs ) ; i ++ {
for i := 0 ; i < len ( t . cs ) ; i ++ {
v := t . cs [ i ]
v := t . cs [ i ]
fmt . Fprintf ( t . out , "%s%s%s%s" ,
fmt . Fprintf ( t . out , "%s%s%s%s" ,
t . pRow ,
t . pRow ,
strings . Repeat ( string ( t . pRow ) , v ) ,
strings . Repeat ( string ( t . pRow ) , v ) ,
t . pRow ,
t . pRow ,
t . pCenter )
t . center ( i ) )
}
}
if nl {
if nl {
fmt . Fprint ( t . out , t . newLine )
fmt . Fprint ( t . out , t . newLine )
@ -266,7 +349,7 @@ func (t Table) printLine(nl bool) {
}
}
// Print line based on row width with our without cell separator
// Print line based on row width with our without cell separator
func ( t Table ) printLineOptionalCellSeparators ( nl bool , displayCellSeparator [ ] bool ) {
func ( t * Table ) printLineOptionalCellSeparators ( nl bool , displayCellSeparator [ ] bool ) {
fmt . Fprint ( t . out , t . pCenter )
fmt . Fprint ( t . out , t . pCenter )
for i := 0 ; i < len ( t . cs ) ; i ++ {
for i := 0 ; i < len ( t . cs ) ; i ++ {
v := t . cs [ i ]
v := t . cs [ i ]
@ -303,43 +386,64 @@ func pad(align int) func(string, string, int) string {
}
}
// Print heading information
// Print heading information
func ( t Table ) printHeading ( ) {
func ( t * Table ) printHeading ( ) {
// Check if headers is available
// Check if headers is available
if len ( t . headers ) < 1 {
if len ( t . headers ) < 1 {
return
return
}
}
// Check if border is set
// Replace with space if not set
fmt . Fprint ( t . out , ConditionString ( t . borders . Left , t . pColumn , SPACE ) )
// Identify last column
// Identify last column
end := len ( t . cs ) - 1
end := len ( t . cs ) - 1
// Get pad function
// Get pad function
padFunc := pad ( t . hAlign )
padFunc := pad ( t . hAlign )
// Print Heading column
// Checking for ANSI escape sequences for header
for i := 0 ; i <= end ; i ++ {
is_esc_seq := false
v := t . cs [ i ]
if len ( t . headerParams ) > 0 {
h := t . headers [ i ]
is_esc_seq = true
}
// Maximum height.
max := t . rs [ headerRowIdx ]
// Print Heading
for x := 0 ; x < max ; x ++ {
// Check if border is set
// Replace with space if not set
fmt . Fprint ( t . out , ConditionString ( t . borders . Left , t . pColumn , SPACE ) )
for y := 0 ; y <= end ; y ++ {
v := t . cs [ y ]
h := ""
if y < len ( t . headers ) && x < len ( t . headers [ y ] ) {
h = t . headers [ y ] [ x ]
}
if t . autoFmt {
if t . autoFmt {
h = Title ( h )
h = Title ( h )
}
}
pad := ConditionString ( ( i == end && ! t . borders . Left ) , SPACE , t . pColumn )
pad := ConditionString ( ( y == end && ! t . borders . Left ) , SPACE , t . pColumn )
if is_esc_seq {
fmt . Fprintf ( t . out , " %s %s" ,
format ( padFunc ( h , SPACE , v ) ,
t . headerParams [ y ] ) , pad )
} else {
fmt . Fprintf ( t . out , " %s %s" ,
fmt . Fprintf ( t . out , " %s %s" ,
padFunc ( h , SPACE , v ) ,
padFunc ( h , SPACE , v ) ,
pad )
pad )
}
}
}
// Next line
// Next line
fmt . Fprint ( t . out , t . newLine )
fmt . Fprint ( t . out , t . newLine )
}
if t . hdrLine {
if t . hdrLine {
t . printLine ( true )
t . printLine ( true )
}
}
}
}
// Print heading information
// Print heading information
func ( t Table ) printFooter ( ) {
func ( t * Table ) printFooter ( ) {
// Check if headers is available
// Check if headers is available
if len ( t . footers ) < 1 {
if len ( t . footers ) < 1 {
return
return
@ -349,9 +453,6 @@ func (t Table) printFooter() {
if ! t . borders . Bottom {
if ! t . borders . Bottom {
t . printLine ( true )
t . printLine ( true )
}
}
// Check if border is set
// Replace with space if not set
fmt . Fprint ( t . out , ConditionString ( t . borders . Bottom , t . pColumn , SPACE ) )
// Identify last column
// Identify last column
end := len ( t . cs ) - 1
end := len ( t . cs ) - 1
@ -359,25 +460,56 @@ func (t Table) printFooter() {
// Get pad function
// Get pad function
padFunc := pad ( t . fAlign )
padFunc := pad ( t . fAlign )
// Print Heading column
// Checking for ANSI escape sequences for header
for i := 0 ; i <= end ; i ++ {
is_esc_seq := false
v := t . cs [ i ]
if len ( t . footerParams ) > 0 {
f := t . footers [ i ]
is_esc_seq = true
}
// Maximum height.
max := t . rs [ footerRowIdx ]
// Print Footer
erasePad := make ( [ ] bool , len ( t . footers ) )
for x := 0 ; x < max ; x ++ {
// Check if border is set
// Replace with space if not set
fmt . Fprint ( t . out , ConditionString ( t . borders . Bottom , t . pColumn , SPACE ) )
for y := 0 ; y <= end ; y ++ {
v := t . cs [ y ]
f := ""
if y < len ( t . footers ) && x < len ( t . footers [ y ] ) {
f = t . footers [ y ] [ x ]
}
if t . autoFmt {
if t . autoFmt {
f = Title ( f )
f = Title ( f )
}
}
pad := ConditionString ( ( i == end && ! t . borders . Top ) , SPACE , t . pColumn )
pad := ConditionString ( ( y == end && ! t . borders . Top ) , SPACE , t . pColumn )
if len ( t . footers [ i ] ) == 0 {
if erasePad [ y ] || ( x == 0 && len ( f ) == 0 ) {
pad = SPACE
pad = SPACE
erasePad [ y ] = true
}
}
if is_esc_seq {
fmt . Fprintf ( t . out , " %s %s" ,
format ( padFunc ( f , SPACE , v ) ,
t . footerParams [ y ] ) , pad )
} else {
fmt . Fprintf ( t . out , " %s %s" ,
fmt . Fprintf ( t . out , " %s %s" ,
padFunc ( f , SPACE , v ) ,
padFunc ( f , SPACE , v ) ,
pad )
pad )
}
}
//fmt.Fprintf(t.out, " %s %s",
// padFunc(f, SPACE, v),
// pad)
}
// Next line
// Next line
fmt . Fprint ( t . out , t . newLine )
fmt . Fprint ( t . out , t . newLine )
//t.printLine(true)
//t.printLine(true)
}
hasPrinted := false
hasPrinted := false
@ -385,7 +517,7 @@ func (t Table) printFooter() {
v := t . cs [ i ]
v := t . cs [ i ]
pad := t . pRow
pad := t . pRow
center := t . pCenter
center := t . pCenter
length := len ( t . footers [ i ] )
length := len ( t . footers [ i ] [ 0 ] )
if length > 0 {
if length > 0 {
hasPrinted = true
hasPrinted = true
@ -398,6 +530,9 @@ func (t Table) printFooter() {
// Print first junction
// Print first junction
if i == 0 {
if i == 0 {
if length > 0 && ! t . borders . Left {
center = t . pRow
}
fmt . Fprint ( t . out , center )
fmt . Fprint ( t . out , center )
}
}
@ -405,18 +540,29 @@ func (t Table) printFooter() {
if length == 0 {
if length == 0 {
pad = SPACE
pad = SPACE
}
}
// Ignore left space of it has printed before
// Ignore left space as it has printed before
if hasPrinted || t . borders . Left {
if hasPrinted || t . borders . Left {
pad = t . pRow
pad = t . pRow
center = t . pCenter
center = t . pCenter
}
}
// Change Center end position
if center != SPACE {
if i == end && ! t . borders . Right {
center = t . pRow
}
}
// Change Center start position
// Change Center start position
if center == SPACE {
if center == SPACE {
if i < end && len ( t . footers [ i + 1 ] ) != 0 {
if i < end && len ( t . footers [ i + 1 ] [ 0 ] ) != 0 {
if ! t . borders . Left {
center = t . pRow
} else {
center = t . pCenter
center = t . pCenter
}
}
}
}
}
// Print the footer
// Print the footer
fmt . Fprintf ( t . out , "%s%s%s%s" ,
fmt . Fprintf ( t . out , "%s%s%s%s" ,
@ -428,22 +574,53 @@ func (t Table) printFooter() {
}
}
fmt . Fprint ( t . out , t . newLine )
fmt . Fprint ( t . out , t . newLine )
}
// Print caption text
func ( t Table ) printCaption ( ) {
width := t . getTableWidth ( )
paragraph , _ := WrapString ( t . captionText , width )
for linecount := 0 ; linecount < len ( paragraph ) ; linecount ++ {
fmt . Fprintln ( t . out , paragraph [ linecount ] )
}
}
// Calculate the total number of characters in a row
func ( t Table ) getTableWidth ( ) int {
var chars int
for _ , v := range t . cs {
chars += v
}
// Add chars, spaces, seperators to calculate the total width of the table.
// ncols := t.colSize
// spaces := ncols * 2
// seps := ncols + 1
return ( chars + ( 3 * t . colSize ) + 2 )
}
}
func ( t Table ) printRows ( ) {
func ( t Table ) printRows ( ) {
for i , lines := range t . lines {
for i , lines := range t . lines {
t . printRow ( lines , i )
t . printRow ( lines , i )
}
}
}
func ( t * Table ) fillAlignment ( num int ) {
if len ( t . columnsAlign ) < num {
t . columnsAlign = make ( [ ] int , num )
for i := range t . columnsAlign {
t . columnsAlign [ i ] = t . align
}
}
}
}
// Print Row Information
// Print Row Information
// Adjust column alignment based on type
// Adjust column alignment based on type
func ( t Table ) printRow ( columns [ ] [ ] string , colKey int ) {
func ( t * Table ) printRow ( columns [ ] [ ] string , rowIdx int ) {
// Get Maximum Height
// Get Maximum Height
max := t . rs [ colKey ]
max := t . rs [ rowIdx ]
total := len ( columns )
total := len ( columns )
// TODO Fix uneven col size
// TODO Fix uneven col size
@ -455,9 +632,15 @@ func (t Table) printRow(columns [][]string, colKey int) {
//}
//}
// Pad Each Height
// Pad Each Height
// pads := []int{}
pads := [ ] int { }
pads := [ ] int { }
// Checking for ANSI escape sequences for columns
is_esc_seq := false
if len ( t . columnsParams ) > 0 {
is_esc_seq = true
}
t . fillAlignment ( total )
for i , line := range columns {
for i , line := range columns {
length := len ( line )
length := len ( line )
pad := max - length
pad := max - length
@ -476,9 +659,14 @@ func (t Table) printRow(columns [][]string, colKey int) {
fmt . Fprintf ( t . out , SPACE )
fmt . Fprintf ( t . out , SPACE )
str := columns [ y ] [ x ]
str := columns [ y ] [ x ]
// Embedding escape sequence with column value
if is_esc_seq {
str = format ( str , t . columnsParams [ y ] )
}
// This would print alignment
// This would print alignment
// Default alignment would use multiple configuration
// Default alignment would use multiple configuration
switch t . align {
switch t . columnsAlign [ y ] {
case ALIGN_CENTER : //
case ALIGN_CENTER : //
fmt . Fprintf ( t . out , "%s" , Pad ( str , SPACE , t . cs [ y ] ) )
fmt . Fprintf ( t . out , "%s" , Pad ( str , SPACE , t . cs [ y ] ) )
case ALIGN_RIGHT :
case ALIGN_RIGHT :
@ -514,7 +702,7 @@ func (t Table) printRow(columns [][]string, colKey int) {
}
}
// Print the rows of the table and merge the cells that are identical
// Print the rows of the table and merge the cells that are identical
func ( t Table ) printRowsMergeCells ( ) {
func ( t * Table ) printRowsMergeCells ( ) {
var previousLine [ ] string
var previousLine [ ] string
var displayCellBorder [ ] bool
var displayCellBorder [ ] bool
var tmpWriter bytes . Buffer
var tmpWriter bytes . Buffer
@ -537,14 +725,19 @@ func (t Table) printRowsMergeCells() {
// Print Row Information to a writer and merge identical cells.
// Print Row Information to a writer and merge identical cells.
// Adjust column alignment based on type
// Adjust column alignment based on type
func ( t Table ) printRowMergeCells ( writer io . Writer , columns [ ] [ ] string , colKey int , previousLine [ ] string ) ( [ ] string , [ ] bool ) {
func ( t * Table ) printRowMergeCells ( writer io . Writer , columns [ ] [ ] string , rowIdx int , previousLine [ ] string ) ( [ ] string , [ ] bool ) {
// Get Maximum Height
// Get Maximum Height
max := t . rs [ colKey ]
max := t . rs [ rowIdx ]
total := len ( columns )
total := len ( columns )
// Pad Each Height
// Pad Each Height
pads := [ ] int { }
pads := [ ] int { }
// Checking for ANSI escape sequences for columns
is_esc_seq := false
if len ( t . columnsParams ) > 0 {
is_esc_seq = true
}
for i , line := range columns {
for i , line := range columns {
length := len ( line )
length := len ( line )
pad := max - length
pad := max - length
@ -555,6 +748,7 @@ func (t Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey i
}
}
var displayCellBorder [ ] bool
var displayCellBorder [ ] bool
t . fillAlignment ( total )
for x := 0 ; x < max ; x ++ {
for x := 0 ; x < max ; x ++ {
for y := 0 ; y < total ; y ++ {
for y := 0 ; y < total ; y ++ {
@ -565,6 +759,11 @@ func (t Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey i
str := columns [ y ] [ x ]
str := columns [ y ] [ x ]
// Embedding escape sequence with column value
if is_esc_seq {
str = format ( str , t . columnsParams [ y ] )
}
if t . autoMergeCells {
if t . autoMergeCells {
//Store the full line to merge mutli-lines cells
//Store the full line to merge mutli-lines cells
fullLine := strings . Join ( columns [ y ] , " " )
fullLine := strings . Join ( columns [ y ] , " " )
@ -580,7 +779,7 @@ func (t Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey i
// This would print alignment
// This would print alignment
// Default alignment would use multiple configuration
// Default alignment would use multiple configuration
switch t . align {
switch t . columnsAlign [ y ] {
case ALIGN_CENTER : //
case ALIGN_CENTER : //
fmt . Fprintf ( writer , "%s" , Pad ( str , SPACE , t . cs [ y ] ) )
fmt . Fprintf ( writer , "%s" , Pad ( str , SPACE , t . cs [ y ] ) )
case ALIGN_RIGHT :
case ALIGN_RIGHT :
@ -614,43 +813,58 @@ func (t Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey i
func ( t * Table ) parseDimension ( str string , colKey , rowKey int ) [ ] string {
func ( t * Table ) parseDimension ( str string , colKey , rowKey int ) [ ] string {
var (
var (
raw [ ] string
raw [ ] string
max int
maxWidth int
)
)
w := DisplayWidth ( str )
// Calculate Width
// Check if with is grater than maximum width
if w > t . mW {
w = t . mW
}
// Check if width exists
raw = getLines ( str )
v , ok := t . cs [ colKey ]
maxWidth = 0
if ! ok || v < w || v == 0 {
for _ , line := range raw {
t . cs [ colKey ] = w
if w := DisplayWidth ( line ) ; w > maxWidth {
maxWidth = w
}
}
if rowKey == - 1 {
return raw
}
}
// Calculate Height
// If wrapping, ensure that all paragraphs in the cell fit in the
// specified width.
if t . autoWrap {
if t . autoWrap {
raw , _ = WrapString ( str , t . cs [ colKey ] )
// If there's a maximum allowed width for wrapping, use that.
} else {
if maxWidth > t . mW {
raw = getLines ( str )
maxWidth = t . mW
}
}
for _ , line := range raw {
// In the process of doing so, we need to recompute maxWidth. This
if w := DisplayWidth ( line ) ; w > max {
// is because perhaps a word in the cell is longer than the
max = w
// allowed maximum width in t.mW.
newMaxWidth := maxWidth
newRaw := make ( [ ] string , 0 , len ( raw ) )
if t . reflowText {
// Make a single paragraph of everything.
raw = [ ] string { strings . Join ( raw , " " ) }
}
for i , para := range raw {
paraLines , _ := WrapString ( para , maxWidth )
for _ , line := range paraLines {
if w := DisplayWidth ( line ) ; w > newMaxWidth {
newMaxWidth = w
}
}
}
if i > 0 {
newRaw = append ( newRaw , " " )
}
newRaw = append ( newRaw , paraLines ... )
}
raw = newRaw
maxWidth = newMaxWidth
}
}
// Make sure the with is the same length as maximum word
// Store the new known maximum width.
// Important for cases where the width is smaller than maxu word
v , ok := t . cs [ colKey ]
if max > t . cs [ colKey ] {
if ! ok || v < maxWidth || v == 0 {
t . cs [ colKey ] = max
t . cs [ colKey ] = maxWidth
}
}
// Remember the number of lines for the row printer.
h := len ( raw )
h := len ( raw )
v , ok = t . rs [ rowKey ]
v , ok = t . rs [ rowKey ]