Documentation
¶
Overview ¶
Package retable provides utilities for working with tabular data in Go, including formatting structs and slices as tables with customizable column naming, cell formatting, and output rendering.
Index ¶
- Variables
- func CallValidateMethod(v reflect.Value) error
- func FormatTableAsStrings(ctx context.Context, table any, formatter CellFormatter, options ...Option) (rows [][]string, err error)
- func FormatViewAsStrings(ctx context.Context, view View, formatter CellFormatter, options ...Option) (rows [][]string, err error)
- func FprintlnTable(w io.Writer, title string, table any) error
- func FprintlnView(w io.Writer, view View) error
- func HasOption(options []Option, option Option) bool
- func IndexedStructFieldAnyValues(structValue reflect.Value, numVals int, indices []int) []any
- func IndexedStructFieldReflectValues(structValue reflect.Value, numVals int, indices []int) []reflect.Value
- func IsNullLike(val reflect.Value) bool
- func IsStringRowEmpty(row []string) bool
- func MustStructFieldIndex(structPtr, fieldPtr any) int
- func ParseTime(str string) (t time.Time, format string, err error)
- func PrintlnTable(title string, table any) error
- func PrintlnView(view View) error
- func RemoveEmptyStringColumns(rows [][]string) (numCols int)
- func RemoveEmptyStringRows(rows [][]string) [][]string
- func SmartAssign(dst, src reflect.Value, dstScanner Scanner, srcFormatter Formatter) (err error)
- func SpaceGoCase(name string) string
- func SpacePascalCase(name string) string
- func SprintlnTable(w io.Writer, title string, table any) (string, error)
- func SprintlnView(w io.Writer, view View) (string, error)
- func StringColumnWidths(rows [][]string, maxCols int) []int
- func StructFieldAnyValues(structValue reflect.Value) []any
- func StructFieldIndex(structPtr, fieldPtr any) (int, error)
- func StructFieldReflectValues(structValue reflect.Value) []reflect.Value
- func StructFieldTypes(structType reflect.Type) (fields []reflect.StructField)
- func UseTitle(columnTitle string) func(fieldName string) (columnTitle string)
- func ViewToStructSlice[T any](view View, naming *StructFieldNaming, dstScanner Scanner, ...) ([]T, error)
- type AnyValuesView
- type CellFormatter
- type CellFormatterFunc
- type ExtraColsView
- type ExtraRowView
- type FilteredView
- type Formatter
- type FormatterFunc
- type HeaderView
- type LayoutFormatter
- type Option
- type Parser
- type PrintfCellFormatter
- type PrintfRawCellFormatter
- type RawCellString
- type RawStringIfTrue
- type ReflectCellView
- func AsReflectCellView(view View) ReflectCellView
- func DerefView(source View) ReflectCellView
- func ExtraColsAnyValueFuncView(left View, columns []string, anyValue func(row, col int) any) ReflectCellView
- func ExtraColsReflectValueFuncView(left View, columns []string, reflectValue func(row, col int) reflect.Value) ReflectCellView
- type ReflectTypeCellFormatter
- func (f *ReflectTypeCellFormatter) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
- func (f *ReflectTypeCellFormatter) WithDefaultFormatter(fmt CellFormatter) *ReflectTypeCellFormatter
- func (f *ReflectTypeCellFormatter) WithInterfaceTypeFormatter(typ reflect.Type, fmt CellFormatter) *ReflectTypeCellFormatter
- func (f *ReflectTypeCellFormatter) WithKindFormatter(kind reflect.Kind, fmt CellFormatter) *ReflectTypeCellFormatter
- func (f *ReflectTypeCellFormatter) WithTypeFormatter(typ reflect.Type, fmt CellFormatter) *ReflectTypeCellFormatter
- type ReflectValuesView
- type Scanner
- type ScannerFunc
- type SingleReflectValueView
- type SprintFormatter
- type StringIfTrue
- type StringParser
- func (p *StringParser) ParseBool(str string) (bool, error)
- func (p *StringParser) ParseDuration(str string) (time.Duration, error)
- func (p *StringParser) ParseFloat(str string) (float64, error)
- func (p *StringParser) ParseInt(str string) (int64, error)
- func (p *StringParser) ParseTime(str string) (time.Time, error)
- func (p *StringParser) ParseUint(str string) (uint64, error)
- type StringsView
- type StringsViewer
- type StructFieldNaming
- func (n *StructFieldNaming) ColumnStructFieldValue(structVal reflect.Value, column string) reflect.Value
- func (n *StructFieldNaming) Columns(strct any) []string
- func (n *StructFieldNaming) IsIgnored(column string) bool
- func (n *StructFieldNaming) NewView(title string, table any) (View, error)
- func (n *StructFieldNaming) String() string
- func (n *StructFieldNaming) StructFieldColumn(field reflect.StructField) string
- func (n *StructFieldNaming) WithIgnore(ignore string) *StructFieldNaming
- func (n *StructFieldNaming) WithTag(tag string) *StructFieldNaming
- type StructRowsView
- type StructRowsViewer
- func (v *StructRowsViewer) NewView(title string, table any) (View, error)
- func (v *StructRowsViewer) String() string
- func (v *StructRowsViewer) WithIgnore(ignore string) *StructRowsViewer
- func (v *StructRowsViewer) WithIgnoreField(structPtr, fieldPtr any) *StructRowsViewer
- func (v *StructRowsViewer) WithIgnoreFieldIndex(fieldIndex int) *StructRowsViewer
- func (v *StructRowsViewer) WithIgnoreFieldIndices(fieldIndices ...int) *StructRowsViewer
- func (v *StructRowsViewer) WithMapIndex(fieldIndex, columnIndex int) *StructRowsViewer
- func (v *StructRowsViewer) WithMapIndices(mapIndices map[int]int) *StructRowsViewer
- func (v *StructRowsViewer) WithTag(tag string) *StructRowsViewer
- type UnsupportedCellFormatter
- type UnsupportedFormatter
- type View
- type Viewer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // DefaultStructFieldNaming provides the default StructFieldNaming configuration // for converting struct fields to table columns. // // Configuration: // - Uses "col" as the struct tag to read column titles // - Ignores fields tagged with "-" // - Uses SpacePascalCase to convert untagged field names to titles // // This is the recommended configuration for most use cases and implements the Viewer interface. // // Example: // // type Person struct { // FirstName string `col:"First Name"` // LastName string `col:"Last Name"` // Age int `col:"Age"` // password string `col:"-"` // ignored (also unexported) // } DefaultStructFieldNaming = StructFieldNaming{ Tag: "col", Ignore: "-", Untagged: SpacePascalCase, } // DefaultStructFieldNamingIgnoreUntagged provides a StructFieldNaming configuration // that only includes explicitly tagged fields. // // Configuration: // - Uses "col" as the struct tag to read column titles // - Ignores fields tagged with "-" // - Ignores all untagged fields (by treating them as "-") // // This is useful when you want strict control over which fields are included in the table, // requiring explicit opt-in via struct tags. Implements the Viewer interface. // // Example: // // type Person struct { // FirstName string `col:"First Name"` // included // LastName string `col:"Last Name"` // included // Age int // excluded (no tag) // internal string `col:"-"` // excluded (tagged with "-") // } DefaultStructFieldNamingIgnoreUntagged = StructFieldNaming{ Tag: "col", Ignore: "-", Untagged: UseTitle("-"), } // SelectViewer is a function variable that selects the most appropriate Viewer implementation // for a given table type. // // Default behavior: // - Returns StringsViewer for [][]string tables // - Returns DefaultStructFieldNaming (as Viewer) for all other types // // This variable can be reassigned to customize viewer selection logic. // The function should return an error if the table type is not supported. // // Example custom selector: // // SelectViewer = func(table any) (Viewer, error) { // switch table.(type) { // case [][]string: // return new(StringsViewer), nil // case []MyCustomType: // return &MyCustomViewer{}, nil // default: // return &DefaultStructFieldNaming, nil // } // } SelectViewer = func(table any) (Viewer, error) { if _, ok := table.([][]string); ok { return new(StringsViewer), nil } return &DefaultStructFieldNaming, nil } )
Functions ¶
func CallValidateMethod ¶
CallValidateMethod is a validation function that can be passed to ViewToStructSlice. It checks if the value implements either Validate() or Valid() methods and calls them.
Validation Method Support:
Validate() error: If the value implements this method, it is called. Any non-nil error returned is propagated as a validation failure.
Valid() bool: If the value implements this method, it is called. If it returns false, an error is created describing the validation failure.
The method checks are performed on v.Interface(), so they work with any type that implements these methods, including pointer receivers.
Parameters:
- v: The reflect.Value to validate. Can be invalid or nil, in which case the function returns nil without error.
Returns:
- error: nil if validation passes, v is invalid, or v doesn't implement validation methods. Non-nil error if validation fails.
Example:
type Email string
func (e Email) Validate() error {
if !strings.Contains(string(e), "@") {
return fmt.Errorf("invalid email: %s", e)
}
return nil
}
type Person struct {
Email Email `db:"email"`
Age int `db:"age"`
}
// CallValidateMethod will call Email.Validate() for the Email field
people, err := ViewToStructSlice[Person](
view,
naming,
nil, nil,
CallValidateMethod,
)
// Example with Valid() bool method
type Age int
func (a Age) Valid() bool {
return a >= 0 && a <= 150
}
type PersonWithAge struct {
Age Age `db:"age"`
}
// CallValidateMethod will call Age.Valid() for the Age field
people, err := ViewToStructSlice[PersonWithAge](
view,
naming,
nil, nil,
CallValidateMethod,
)
func FormatTableAsStrings ¶
func FormatTableAsStrings(ctx context.Context, table any, formatter CellFormatter, options ...Option) (rows [][]string, err error)
FormatTableAsStrings converts any table data into a 2D string slice.
This function automatically selects an appropriate Viewer for the table type, creates a View, and then formats it as strings using the provided formatter and options.
Parameters:
- ctx: Context for cancellation and timeout control during formatting
- table: The table data to format (e.g., [][]string, []Person, etc.)
- formatter: Optional CellFormatter to customize cell rendering (can be nil for default formatting)
- options: Optional formatting options (e.g., OptionAddHeaderRow)
Returns a 2D string slice where each inner slice represents a row, and an error if viewer selection, view creation, or formatting fails.
Example:
type Person struct {
Name string
Age int
}
people := []Person{{Name: "John", Age: 30}, {Name: "Alice", Age: 25}}
rows, err := FormatTableAsStrings(ctx, people, nil, OptionAddHeaderRow)
if err != nil {
log.Fatal(err)
}
// rows[0] = ["Name", "Age"] // header row
// rows[1] = ["John", "30"] // data row
// rows[2] = ["Alice", "25"] // data row
func FormatViewAsStrings ¶
func FormatViewAsStrings(ctx context.Context, view View, formatter CellFormatter, options ...Option) (rows [][]string, err error)
FormatViewAsStrings converts a View into a 2D string slice.
This function iterates through all rows and columns in the view, formatting each cell using the provided CellFormatter. If no formatter is provided, TryFormattersOrSprint is used as the default.
Parameters:
- ctx: Context for cancellation and timeout control during formatting
- view: The View to format, providing access to rows, columns, and cell data
- formatter: Optional CellFormatter to customize cell rendering (can be nil for default formatting)
- options: Optional formatting options (e.g., OptionAddHeaderRow to include column titles)
When OptionAddHeaderRow is set, the column titles from view.Columns() are added as the first row, also passed through the formatter for consistent formatting.
Returns a 2D string slice where each inner slice represents a row, and an error if any cell formatting fails.
Example:
rows, err := FormatViewAsStrings(ctx, myView, nil, OptionAddHeaderRow)
if err != nil {
log.Fatal(err)
}
// Process the formatted rows
for _, row := range rows {
fmt.Println(strings.Join(row, " | "))
}
func FprintlnTable ¶
FprintlnTable formats and writes any table data as a human-readable table to the provided io.Writer.
The function automatically selects an appropriate Viewer for the table type, creates a View with the given title, and then formats it using FprintlnView.
Supported table types include [][]string, struct slices, and any type with a registered Viewer.
Parameters:
- w: The io.Writer to write the formatted table to
- title: The title to display above the table (can be empty)
- table: The table data to format (e.g., [][]string, []Person, etc.)
Returns an error if the table type is not supported, if view creation fails, or if writing fails.
Example:
people := []Person{
{Name: "John", Age: 30},
{Name: "Alice", Age: 25},
}
err := FprintlnTable(os.Stdout, "People", people)
func FprintlnView ¶
FprintlnView formats and writes a View as a human-readable table to the provided io.Writer.
The table is formatted with pipe-separated columns, aligned based on the maximum width of each column. If the view has a title, it is printed first on a separate line. A header row is automatically added using the view's column names.
The output format looks like:
Title: | Column1 | Column2 | Column3 | | value1 | value2 | value3 |
Returns an error if formatting fails or if writing to the io.Writer fails.
Example:
var buf bytes.Buffer
err := FprintlnView(&buf, myView)
if err != nil {
log.Fatal(err)
}
fmt.Print(buf.String())
func HasOption ¶
HasOption checks whether any Option in the slice has the specified option flag set. Returns true if at least one Option in the slice contains the given option.
This is a convenience function for checking options in a slice of Options, which is commonly used when passing variadic option parameters.
Example:
options := []Option{OptionAddHeaderRow}
if HasOption(options, OptionAddHeaderRow) {
// Header row option is present in the slice
}
// With variadic parameters:
func formatTable(data any, options ...Option) {
if HasOption(options, OptionAddHeaderRow) {
// Add header row
}
}
func IndexedStructFieldAnyValues ¶
IndexedStructFieldAnyValues returns a reordered subset of exported struct field values as []any, including the inlined fields of any anonymously embedded structs.
The indices parameter maps from the flattened field list to the desired output positions. A negative index value (-1) indicates that the field should be skipped. The numVals parameter specifies the size of the output slice.
This is similar to IndexedStructFieldReflectValues but returns values as any interfaces.
Panics if the length of indices does not match the number of fields in the struct.
Example:
type Person struct {
Name string
Age int
City string
}
p := Person{Name: "John", Age: 30, City: "NYC"}
// Reorder to [City, Name] and skip Age
values := IndexedStructFieldAnyValues(reflect.ValueOf(p), 2, []int{1, -1, 0})
// Returns: []any{NYC, John}
func IndexedStructFieldReflectValues ¶
func IndexedStructFieldReflectValues(structValue reflect.Value, numVals int, indices []int) []reflect.Value
IndexedStructFieldReflectValues returns a reordered subset of reflect.Value for exported struct fields, including the inlined fields of any anonymously embedded structs.
The indices parameter maps from the flattened field list to the desired output positions. A negative index value (-1) indicates that the field should be skipped. The numVals parameter specifies the size of the output slice.
Panics if the length of indices does not match the number of fields in the struct.
Example:
type Person struct {
Name string
Age int
City string
}
p := Person{Name: "John", Age: 30, City: "NYC"}
// Reorder to [City, Name] and skip Age
values := IndexedStructFieldReflectValues(reflect.ValueOf(p), 2, []int{1, -1, 0})
// Returns reflect.Values at positions: [0]=City, [1]=Name
func IsNullLike ¶
IsNullLike returns true if the passed reflect.Value should be treated as null-like.
A value is considered null-like if it fulfills any of the following conditions:
- Is not valid (zero value of reflect.Value)
- Is nil (for pointer, interface, slice, map, channel, func, or unsafe pointer types)
- Is of type struct{} (empty struct)
- Implements IsNull() bool method which returns true
- Implements IsZero() bool method which returns true
- Implements driver.Valuer interface which returns (nil, nil)
This function is useful for determining whether a value should be rendered as NULL or empty in table output, supporting various nullable type conventions used in Go.
Example:
var ptr *string
IsNullLike(reflect.ValueOf(ptr)) // true (nil pointer)
type Nullable struct{}
func (n Nullable) IsNull() bool { return true }
IsNullLike(reflect.ValueOf(Nullable{})) // true
IsNullLike(reflect.ValueOf(42)) // false
func IsStringRowEmpty ¶
IsStringRowEmpty returns true if all cells in the row are empty strings or if the length of the row is zero.
This is useful for filtering out empty rows from table data before rendering or processing.
Example:
IsStringRowEmpty([]string{}) // true
IsStringRowEmpty([]string{"", "", ""}) // true
IsStringRowEmpty([]string{"", "data", ""}) // false
func MustStructFieldIndex ¶
MustStructFieldIndex returns the index of the struct field pointed to by fieldPtr within the struct pointed to by structPtr.
This is the panic-on-error version of StructFieldIndex. The returned index counts exported struct fields including the inlined fields of any anonymously embedded structs, using the same ordering as StructFieldTypes.
Panics if either parameter is invalid or if the field cannot be found.
Example:
type Person struct {
Name string
Age int
}
p := Person{Name: "John", Age: 30}
index := MustStructFieldIndex(&p, &p.Age)
// Returns: 1
func ParseTime ¶
ParseTime is a standalone function that parses a time string and returns both the parsed time and the format that successfully parsed it.
This is useful when you need to know which format was used, for example to maintain consistent formatting when round-tripping time values, or for validation purposes.
The function tries all formats in the package-level timeFormats list in order.
Parameters:
- str: The string to parse
Returns:
- t: The parsed time.Time value
- format: The layout string that successfully parsed the time
- err: Error if no format could parse the string
Example:
t, format, err := ParseTime("2024-03-15T14:30:00Z")
// t = time value, format = "2006-01-02T15:04:05Z07:00" (RFC3339), err = nil
t, format, err := ParseTime("2024-03-15")
// t = time value, format = "2006-01-02" (DateOnly), err = nil
// Use the format to maintain consistency
formatted := t.Format(format) // Returns string in same format as input
func PrintlnTable ¶
PrintlnTable formats and prints any table data as a human-readable table to standard output.
This is a convenience wrapper around FprintlnTable that writes to os.Stdout. See FprintlnTable for details on supported table types.
Parameters:
- title: The title to display above the table (can be empty)
- table: The table data to format
Returns any error that occurred during formatting or writing.
Example:
people := []Person{
{Name: "John", Age: 30},
{Name: "Alice", Age: 25},
}
err := PrintlnTable("People", people)
Example ¶
type Row struct {
A string
B int
C *time.Time
}
t := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)
PrintlnTable("ExamplePrintlnTable", []Row{
{A: "1", B: -1, C: &t},
{A: "", B: 2222222222, C: nil},
{A: "Last row", B: 0, C: nil},
})
Output: ExamplePrintlnTable: | A | B | C | | 1 | -1 | 2024-01-02 03:04:05 +0000 UTC | | | 2222222222 | | | Last row | 0 | |
func PrintlnView ¶
PrintlnView formats and prints a View as a human-readable table to standard output.
This is a convenience wrapper around FprintlnView that writes to os.Stdout. See FprintlnView for format details.
Returns any error that occurred during formatting or writing.
Example:
err := PrintlnView(myView)
if err != nil {
log.Fatal(err)
}
Example ¶
PrintlnView(&StringsView{
Tit: "ExamplePrintlnView",
Cols: []string{"A", "B", "C"},
Rows: [][]string{
{"1", "2222222222", "3"},
{"", "", "3333"},
{"Last row"},
},
})
Output: ExamplePrintlnView: | A | B | C | | 1 | 2222222222 | 3 | | | | 3333 | | Last row | | |
func RemoveEmptyStringColumns ¶
RemoveEmptyStringColumns removes all columns that contain only empty strings from the input rows and returns the new number of columns.
The function modifies the input rows slice in-place by removing columns that are empty across all rows. It processes columns from right to left to maintain correct indexing during removal.
Returns the number of columns remaining after removal.
Example:
rows := [][]string{
{"Name", "", "Age", ""},
{"John", "", "30", ""},
{"Alice", "", "25", ""},
}
numCols := RemoveEmptyStringColumns(rows)
// Modifies rows to: [["Name", "Age"], ["John", "30"], ["Alice", "25"]]
// Returns: 2
func RemoveEmptyStringRows ¶
RemoveEmptyStringRows removes all rows that contain only empty strings from the input slice and returns the modified slice.
The function modifies the input slice in-place by removing empty rows. Rows are checked using IsStringRowEmpty and removed in reverse order to maintain correct indexing during removal.
Example:
rows := [][]string{
{"Name", "Age"},
{"", ""}, // Will be removed
{"John", "30"},
{"", ""}, // Will be removed
}
result := RemoveEmptyStringRows(rows)
// Returns: [["Name", "Age"], ["John", "30"]]
func SmartAssign ¶
SmartAssign performs intelligent type conversion when assigning src to dst. It attempts multiple conversion strategies in order of preference, making it suitable for converting between different types in data mapping scenarios.
Type Conversion Strategies (in order):
Null handling: If src implements IsNull() bool and returns true, dst is set to its zero value.
Direct conversion: If src type is convertible to dst type using reflect.Value.Convert, the conversion is performed directly.
Nil pointer handling: If src is a nil pointer, dst is set to its zero value.
Custom formatting: If dst is a string type and srcFormatter is provided, srcFormatter.Format is used to convert src to string.
TextMarshaler: If src implements encoding.TextMarshaler, its MarshalText method is used to get a text representation for further conversion.
Stringer: If src implements fmt.Stringer, its String method is used to get a string representation for further conversion.
Time parsing: If src is a string and dst is time.Time or *time.Time, ParseTime is used to convert the string to a time value.
Pointer dereferencing: If src is a non-nil pointer, SmartAssign is recursively called with the dereferenced value.
Empty struct handling: If src is an empty struct (struct{}), dst is set to its zero value.
Boolean conversions: - bool to numeric types: true becomes 1, false becomes 0 - bool to string: "true" or "false" - numeric types to bool: non-zero becomes true, zero becomes false - string to bool: parsed using strconv.ParseBool
String to numeric conversions: - String to int/uint: parsed using strconv.ParseInt/ParseUint - String to float: parsed using strconv.ParseFloat
Fallback string conversion: Any type can be converted to string using fmt.Sprint as a last resort.
Pointer allocation: If dst is a pointer type and previous strategies failed, a new instance is created and SmartAssign is recursively called to assign to the dereferenced pointer.
Parameters:
- dst: The destination reflect.Value to assign to. Must be valid and settable.
- src: The source reflect.Value to assign from. Must be valid.
- dstScanner: Optional Scanner for custom string-to-type conversions (can be nil).
- srcFormatter: Optional Formatter for custom type-to-string conversions (can be nil).
Returns:
- error: nil on success, or an error describing why the assignment failed. Returns errors.ErrUnsupported if no conversion strategy could handle the type combination.
Example:
// Convert string to int
var result int
dst := reflect.ValueOf(&result).Elem()
src := reflect.ValueOf("42")
err := SmartAssign(dst, src, nil, nil)
// result == 42
// Convert bool to string
var str string
dst = reflect.ValueOf(&str).Elem()
src = reflect.ValueOf(true)
err = SmartAssign(dst, src, nil, nil)
// str == "true"
// Convert with custom formatter
formatter := FormatterFunc(func(v reflect.Value) (string, error) {
return fmt.Sprintf("#%v", v.Interface()), nil
})
var output string
dst = reflect.ValueOf(&output).Elem()
src = reflect.ValueOf(42)
err = SmartAssign(dst, src, nil, formatter)
// output == "#42"
func SpaceGoCase ¶
SpaceGoCase inserts spaces before upper case characters within Go-style names, with special handling for acronyms and abbreviations.
The function processes each character and:
- Inserts a space before uppercase letters following lowercase letters ("CamelCase" -> "Camel Case")
- Detects acronyms by recognizing consecutive uppercase letters
- Inserts a space before the last uppercase letter in an acronym when followed by lowercase ("HTTPServer" -> "HTTP Server")
- Replaces underscore '_' characters with spaces
- Avoids duplicate spaces
- Trims leading and trailing spaces from the result
This is more sophisticated than SpacePascalCase as it properly handles Go naming conventions where acronyms like HTTP, API, URL are kept in uppercase. Can be used as the UntaggedTitle function in StructFieldNaming.
Example:
SpaceGoCase("FirstName") // Returns: "First Name"
SpaceGoCase("HTTPServer") // Returns: "HTTP Server"
SpaceGoCase("APIKey") // Returns: "API Key"
SpaceGoCase("URLPath") // Returns: "URL Path"
SpaceGoCase("user_name") // Returns: "user name"
func SpacePascalCase ¶
SpacePascalCase inserts spaces before upper case characters within PascalCase names.
The function processes each character in the input string and:
- Inserts a space before each uppercase letter that follows a lowercase letter or non-space
- Replaces underscore '_' characters with spaces
- Avoids duplicate spaces
- Trims leading and trailing spaces from the result
This is particularly useful for converting struct field names to human-readable column titles. Can be used as the UntaggedTitle function in StructFieldNaming.
Example:
SpacePascalCase("FirstName") // Returns: "First Name"
SpacePascalCase("HTTPServer") // Returns: "H T T P Server"
SpacePascalCase("user_name") // Returns: "user name"
SpacePascalCase("API_Key") // Returns: "A P I Key"
func SprintlnTable ¶
SprintlnTable formats any table data as a human-readable table and returns it as a string.
This is a convenience wrapper around FprintlnTable that writes to a strings.Builder and returns the resulting string. See FprintlnTable for details on supported table types.
Parameters:
- w: Ignored parameter (kept for signature compatibility)
- title: The title to display above the table (can be empty)
- table: The table data to format
Returns the formatted table string and any error that occurred.
Example:
people := []Person{{Name: "John", Age: 30}}
str, err := SprintlnTable(nil, "People", people)
if err != nil {
log.Fatal(err)
}
fmt.Print(str)
func SprintlnView ¶
SprintlnView formats a View as a human-readable table and returns it as a string.
This is a convenience wrapper around FprintlnView that writes to a strings.Builder and returns the resulting string. See FprintlnView for format details.
Returns the formatted table string and any error that occurred during formatting.
Example:
str, err := SprintlnView(myView)
if err != nil {
log.Fatal(err)
}
fmt.Print(str)
func StringColumnWidths ¶
StringColumnWidths returns the maximum width of each column in a string table, measured as the count of UTF-8 runes (not bytes).
The maxCols parameter limits the number of columns to consider:
- If maxCols is -1, all columns across all rows are considered
- If maxCols is positive, only the first maxCols columns are processed
This is useful for formatting tables with aligned columns, ensuring each column is wide enough to accommodate its widest value.
Returns a slice where each element represents the maximum width of that column. Returns nil if maxCols is 0.
Example:
rows := [][]string{
{"Name", "Age", "City"},
{"John", "30", "New York"},
{"Alice", "25", "SF"},
}
widths := StringColumnWidths(rows, -1)
// Returns: [5, 3, 8] (lengths of "Alice", "Age", "New York")
func StructFieldAnyValues ¶
StructFieldAnyValues returns the values of exported struct fields as []any, including the inlined fields of any anonymously embedded structs. If structValue is a pointer, it automatically dereferences to the underlying struct value.
This is similar to StructFieldReflectValues but returns the values as any interfaces instead of reflect.Value, making it easier to work with in non-reflection contexts.
The function recursively processes embedded structs, flattening their field values into the result. Only exported fields are included. The order matches StructFieldTypes.
Example:
type Person struct {
Name string
Age int
}
p := Person{Name: "John", Age: 30}
values := StructFieldAnyValues(reflect.ValueOf(p))
// Returns: []any{"John", 30}
func StructFieldIndex ¶
StructFieldIndex returns the index of the struct field pointed to by fieldPtr within the struct pointed to by structPtr.
The returned index counts exported struct fields including the inlined fields of any anonymously embedded structs, using the same ordering as StructFieldTypes.
Both structPtr and fieldPtr must be non-nil pointers. Returns an error if either parameter is nil, not a pointer, or if the field cannot be found within the struct.
Example:
type Person struct {
Name string
Age int
}
p := Person{Name: "John", Age: 30}
index, err := StructFieldIndex(&p, &p.Age)
// Returns: index=1, err=nil
func StructFieldReflectValues ¶
StructFieldReflectValues returns the reflect.Value of exported struct fields, including the inlined fields of any anonymously embedded structs. If structValue is a pointer, it automatically dereferences to the underlying struct value.
The function recursively processes embedded structs, flattening their field values into the result. Only exported fields (those starting with an uppercase letter) are included. The order of fields matches the order returned by StructFieldTypes.
Example:
type Address struct {
Street string
City string
}
type Person struct {
Name string
Address
}
p := Person{Name: "John", Address: Address{Street: "Main St", City: "NYC"}}
values := StructFieldReflectValues(reflect.ValueOf(p))
// Returns reflect.Values for: ["John", "Main St", "NYC"]
func StructFieldTypes ¶
func StructFieldTypes(structType reflect.Type) (fields []reflect.StructField)
StructFieldTypes returns the exported fields of a struct type, including the inlined fields of any anonymously embedded structs. If structType is a pointer, it automatically dereferences to the underlying struct type.
The function recursively processes embedded structs, flattening their fields into the result. Only exported fields (those starting with an uppercase letter) are included.
Example:
type Address struct {
Street string
City string
}
type Person struct {
Name string
Address // embedded struct
age int // unexported, will be excluded
}
fields := StructFieldTypes(reflect.TypeOf(Person{}))
// Returns: [Name, Street, City]
func UseTitle ¶
UseTitle returns a function that always returns the same fixed columnTitle, ignoring the input fieldName parameter.
This is useful when you want to provide a constant column title regardless of the field name, or as a way to ignore certain fields by returning a special marker like "-". Can be used as the UntaggedTitle function in StructFieldNaming.
Example:
// Create a function that always returns "Fixed Title"
titleFunc := UseTitle("Fixed Title")
title := titleFunc("AnyFieldName") // Returns: "Fixed Title"
// Common pattern to ignore untagged fields
naming := StructFieldNaming{
Tag: "col",
Ignore: "-",
Untagged: UseTitle("-"), // Ignore all untagged fields
}
func ViewToStructSlice ¶
func ViewToStructSlice[T any](view View, naming *StructFieldNaming, dstScanner Scanner, srcFormatter Formatter, validate func(reflect.Value) error, requiredCols ...string) ([]T, error)
ViewToStructSlice converts a View into a strongly-typed slice of structs. It maps each row in the View to a struct instance by matching column names to struct field names using the provided naming conventions.
Type Parameter:
- T: The target struct type. Can be either a struct type (MyStruct) or a pointer to struct (*MyStruct). The function will handle both cases appropriately.
Column to Field Mapping:
The function uses StructFieldNaming to determine how View column names map to struct field names. For each column in the View, it attempts to find a corresponding struct field and assigns the value using SmartAssign, which performs intelligent type conversions.
If a column has no corresponding struct field, it is silently skipped. This allows Views to contain extra columns that aren't needed in the struct.
Required Columns:
The requiredCols parameter specifies column names that MUST exist both in the View and as struct fields. If any required column is missing from either the View or the struct definition, an error is returned immediately before processing any rows.
Type Conversion:
SmartAssign is used for each field assignment, enabling automatic conversion between compatible types:
- String to numeric types (int, float, etc.)
- Numeric to string
- Boolean to numeric (true=1, false=0)
- Custom conversions via dstScanner and srcFormatter
Validation:
After each successful field assignment, if a validate function is provided, it is called with the struct field value. If validation fails, processing stops and the error is returned. This allows for field-level validation during the conversion process.
Use CallValidateMethod as the validate function to automatically invoke Validate() or Valid() methods on struct field values that implement them.
Parameters:
- view: The source View to convert. Must not be nil.
- naming: Defines how column names map to struct field names. Must not be nil.
- dstScanner: Optional Scanner for custom string-to-type conversions (can be nil).
- srcFormatter: Optional Formatter for custom type-to-string conversions (can be nil).
- validate: Optional function called after each field assignment for validation (can be nil).
- requiredCols: Column names that must exist in both View and struct.
Returns:
- []T: A slice of structs with length equal to view.NumRows(). Each struct represents one row from the View.
- error: An error if:
- T is not a struct or pointer to struct
- Any required column is missing from View or struct
- Type conversion fails for any field
- Validation fails for any field
Example:
// Define a struct to hold row data
type Person struct {
Name string `db:"name"`
Age int `db:"age"`
}
// Create a View with data
view := NewStringsView("people",
[][]string{
{"Alice", "30"},
{"Bob", "25"},
},
"name", "age")
// Convert to slice of structs
naming := &StructFieldNaming{Tag: "db"}
people, err := ViewToStructSlice[Person](view, naming, nil, nil, nil)
// people[0] == Person{Name: "Alice", Age: 30}
// people[1] == Person{Name: "Bob", Age: 25}
// With required columns and validation
people, err = ViewToStructSlice[Person](
view,
naming,
nil, nil,
CallValidateMethod, // Validate each field
"name", "age", // Both columns are required
)
// Using pointer type
people, err := ViewToStructSlice[*Person](view, naming, nil, nil, nil)
// people[0] == &Person{Name: "Alice", Age: 30}
Types ¶
type AnyValuesView ¶
type AnyValuesView struct {
// Tit is the title of this view, returned by the Title() method.
Tit string
// Cols contains the column names defining both the column headers
// and the number of columns in this view.
Cols []string
// Rows contains the data rows, where each row is a slice of any-typed values.
// Each cell can hold a value of any type.
Rows [][]any
}
AnyValuesView is a View implementation that stores cells as values of any type.
Unlike StringsView which is restricted to string values, AnyValuesView can hold heterogeneous data types within the same table. Each cell can contain values of different types (int, string, bool, struct, etc.), making it ideal for mixed-type data or when converting from other view types.
The underlying data structure uses [][]any, where each row is a slice of interface{} (any) values. This provides maximum flexibility at the cost of type safety and some performance overhead due to interface boxing.
Performance characteristics:
- Direct memory access: O(1) for cell retrieval
- Interface boxing overhead for value storage and retrieval
- Higher memory usage than StringsView due to interface overhead
- Type assertions required when accessing specific types
When to use AnyValuesView:
- Working with mixed-type tabular data (numbers, strings, booleans, etc.)
- Caching data from another View with NewAnyValuesViewFrom
- Need to store arbitrary Go values in table cells
- Converting between different view types
- Building dynamic tables where cell types aren't known at compile time
Example usage:
view := &retable.AnyValuesView{
Tit: "Mixed Data",
Cols: []string{"ID", "Name", "Active", "Score"},
Rows: [][]any{
{1, "Alice", true, 95.5},
{2, "Bob", false, 87.3},
},
}
// Type assertion needed for specific types
if score, ok := view.Cell(0, 3).(float64); ok {
fmt.Printf("Score: %.1f\n", score)
}
Thread safety: Not thread-safe. External synchronization required for concurrent access.
func NewAnyValuesViewFrom ¶
func NewAnyValuesViewFrom(source View) *AnyValuesView
NewAnyValuesViewFrom creates an AnyValuesView by reading and caching all cells from the source View.
This function materializes all data from the source view into memory as an AnyValuesView. This is useful for:
- Caching data from views with expensive Cell() operations
- Converting computed or dynamic views into static data
- Creating a snapshot of view data at a point in time
- Ensuring consistent data access when the source might change
The function calls source.Cell() for every cell in the view, so for large views this can be memory-intensive. The resulting view is completely independent of the source.
Parameters:
- source: The View to read data from
Returns a new AnyValuesView containing all data from the source.
Example:
// Cache an expensive computed view expensiveView := retable.NewComputedView(...) cached := retable.NewAnyValuesViewFrom(expensiveView) // Now cached can be accessed multiple times without recomputation
Time complexity: O(rows * cols) for copying all cells Space complexity: O(rows * cols) for storing all values
func (*AnyValuesView) Cell ¶
func (view *AnyValuesView) Cell(row, col int) any
Cell returns the value at the specified row and column indices.
The returned value can be of any type depending on what was stored in that cell. Callers typically need to use type assertions or type switches to work with the specific types.
Parameters:
- row: Zero-based row index (0 to NumRows()-1)
- col: Zero-based column index (0 to len(Columns())-1)
Returns:
- The cell value (of any type) if indices are valid
- nil if row or col indices are out of bounds
Example with type assertion:
value := view.Cell(0, 2)
switch v := value.(type) {
case int:
fmt.Printf("Integer: %d\n", v)
case string:
fmt.Printf("String: %s\n", v)
case bool:
fmt.Printf("Boolean: %t\n", v)
}
Time complexity: O(1)
func (*AnyValuesView) Columns ¶
func (view *AnyValuesView) Columns() []string
Columns returns the column names of this view.
func (*AnyValuesView) NumRows ¶
func (view *AnyValuesView) NumRows() int
NumRows returns the number of data rows in this view.
func (*AnyValuesView) Title ¶
func (view *AnyValuesView) Title() string
Title returns the title of this view.
type CellFormatter ¶
type CellFormatter interface {
// FormatCell formats the view cell at a row/col position as string.
//
// Returns errors.ErrUnsupported if this formatter doesn't support the cell's type,
// signaling that alternative formatters should be tried. Other errors indicate
// actual formatting failures.
//
// The raw result indicates whether the returned string is in the raw format of
// the target table format and can be used as-is (true), or if it needs to be
// sanitized according to the output format (false).
//
// Parameters:
// - ctx: Context for cancellation and timeout control
// - view: The table view containing the cell to format
// - row: Zero-based row index
// - col: Zero-based column index
//
// Returns:
// - str: The formatted string representation
// - raw: Whether the string is format-specific raw output
// - err: Error if formatting failed, or errors.ErrUnsupported if unsupported
FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
}
CellFormatter is the primary interface for formatting table cell values as strings. This is the higher-level interface compared to Formatter, as it operates on cells within a table View context and supports format-specific "raw" output.
The CellFormatter design supports a sophisticated formatting pipeline where multiple formatters can be tried in sequence until one successfully formats a value. Formatters signal their inability to handle a value by returning errors.ErrUnsupported, which allows the caller to try alternative formatters.
The "raw" result concept is crucial for output format optimization: when raw is true, the string can be used directly in the output format without escaping or sanitization (e.g., HTML tags in HTML output, CSV-formatted values in CSV output). When raw is false, the string should be sanitized according to the output format (e.g., HTML-escaped).
Design pattern:
// Try formatters in priority order
str, raw, err := formatter1.FormatCell(ctx, view, row, col)
if errors.Is(err, errors.ErrUnsupported) {
str, raw, err = formatter2.FormatCell(ctx, view, row, col)
}
if err != nil {
// handle error or use fallback
}
Example usage:
formatter := PrintfCellFormatter("%.2f")
str, raw, err := formatter.FormatCell(ctx, view, 0, 0)
if err != nil {
// handle error
}
if !raw {
str = html.EscapeString(str) // sanitize for HTML output
}
func CellFormatterFromFormatter ¶
func CellFormatterFromFormatter(f Formatter, rawResult bool) CellFormatter
CellFormatterFromFormatter adapts a simple Formatter to work as a CellFormatter. This bridge function allows reflection-based value formatters to be used in the table cell formatting system.
The rawResult parameter determines whether the formatted strings should be marked as "raw" (true) or requiring sanitization (false) in the table output format.
This adapter extracts the reflect.Value from the specified cell position and passes it to the underlying Formatter.
Parameters:
- f: The Formatter to adapt
- rawResult: Whether the formatted output is considered raw (doesn't need escaping)
Returns:
- A CellFormatter that delegates to the provided Formatter
Example:
formatter := SprintFormatter{}
cellFormatter := CellFormatterFromFormatter(formatter, false)
// cellFormatter can now be used in table rendering
func SprintCellFormatter ¶
func SprintCellFormatter(rawResult bool) CellFormatter
SprintCellFormatter returns a universal CellFormatter that formats any cell value using fmt.Sprint, which uses the value's String() method if available, or falls back to Go's default formatting.
This formatter never returns errors.ErrUnsupported and accepts all value types, making it an ideal fallback formatter at the end of a formatting chain.
The rawResult parameter determines whether the formatted output should be marked as raw (true) or requiring sanitization (false). This allows the caller to control the raw flag based on the output format requirements.
Parameters:
- rawResult: Whether formatted strings should be marked as raw output
Returns:
- A CellFormatter that uses fmt.Sprint for all values
Example usage:
// Non-raw formatter (strings need sanitization) formatter := SprintCellFormatter(false) str, raw, _ := formatter.FormatCell(ctx, view, 0, 0) // str contains fmt.Sprint output, raw == false // Raw formatter (strings are pre-formatted) rawFormatter := SprintCellFormatter(true) str, raw, _ := rawFormatter.FormatCell(ctx, view, 0, 0) // str contains fmt.Sprint output, raw == true
func TryFormattersOrSprint ¶
func TryFormattersOrSprint(formatters ...CellFormatter) CellFormatter
TryFormattersOrSprint creates a composite CellFormatter that tries multiple formatters in sequence until one succeeds, with fmt.Sprint as the ultimate fallback.
This is the primary function for building flexible formatting chains. It implements a "chain of responsibility" pattern where each formatter is tried in order until one successfully formats the value (returns no error or a non-ErrUnsupported error).
Behavior:
- Tries each formatter in the order provided
- Continues to next formatter only if current returns errors.ErrUnsupported
- Returns immediately on success or non-ErrUnsupported errors
- Falls back to fmt.Sprint if all formatters return ErrUnsupported
- Returns empty string for nil values
- Ignores nil formatters in the list
- Fallback results are always marked as non-raw (raw=false)
This function is essential for type-safe formatting where you want to try specialized formatters first and fall back to generic formatting if the value type doesn't match.
Parameters:
- formatters: Variable number of CellFormatters to try in order
Returns:
- A composite CellFormatter that implements the fallback chain
Example usage:
formatter := TryFormattersOrSprint(
PrintfCellFormatter("%.2f"), // Try formatting as float with 2 decimals
LayoutFormatter("2006-01-02"), // Try formatting as date
// Falls back to fmt.Sprint for other types
)
str, raw, err := formatter.FormatCell(ctx, view, 0, 0)
// Uses first matching formatter, or fmt.Sprint if none match
type CellFormatterFunc ¶
type CellFormatterFunc func(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
CellFormatterFunc is a function type that implements the CellFormatter interface, allowing plain functions to be used as CellFormatters.
This adapter type follows the common Go pattern of defining a function type that implements an interface (similar to http.HandlerFunc), making it easy to create inline formatters without defining separate types.
Example:
formatter := CellFormatterFunc(func(ctx context.Context, view View, row, col int) (string, bool, error) {
val := view.Cell(row, col)
if num, ok := val.(int); ok {
return fmt.Sprintf("#%05d", num), false, nil
}
return "", false, errors.ErrUnsupported
})
func ReflectCellFormatterFunc ¶
func ReflectCellFormatterFunc(function any, rawResult bool) (formatter CellFormatterFunc, valType reflect.Type, err error)
ReflectCellFormatterFunc converts an arbitrary function into a CellFormatterFunc using reflection, enabling type-safe cell formatting with minimal boilerplate.
This powerful function allows you to write formatters as simple, strongly-typed functions that work with specific value types, and automatically adapts them to work with the generic CellFormatter interface. This provides both type safety and ergonomic API.
Function signature requirements:
Arguments (0-2 parameters):
- Optional first parameter: context.Context (if present, must be first)
- Optional value parameter: Any type (becomes the valType return value)
Results (1-2 return values):
- First result: Must be string (the formatted output)
- Optional second result: Must be error (formatting error)
The rawResult parameter determines the raw flag returned by the generated formatter.
Parameters:
- function: The function to convert (validated via reflection)
- rawResult: Whether the formatter should mark output as raw
Returns:
- formatter: The generated CellFormatterFunc
- valType: The reflect.Type of the value parameter (for type registration)
- err: Error if function signature is invalid
Example usage:
// Simple type-safe formatter
formatter, typ, err := ReflectCellFormatterFunc(
func(t time.Time) string {
return t.Format("2006-01-02")
},
false,
)
// typ == reflect.TypeOf(time.Time{})
// formatter is a CellFormatterFunc that only works with time.Time
// With context and error handling
formatter, typ, err := ReflectCellFormatterFunc(
func(ctx context.Context, val CustomType) (string, error) {
if err := ctx.Err(); err != nil {
return "", err
}
return val.Format(), nil
},
false,
)
// No arguments (formats any value the same way)
formatter, _, err := ReflectCellFormatterFunc(
func() string { return "constant" },
true,
)
func (CellFormatterFunc) FormatCell ¶
func (f CellFormatterFunc) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements the CellFormatter interface by calling the function itself.
type ExtraColsView ¶
type ExtraColsView []View
ExtraColsView is a zero-copy decorator that horizontally concatenates multiple Views, combining their columns into a single unified View. This is the columnar equivalent of a SQL JOIN operation, allowing you to merge data from different sources side-by-side.
Key Features ¶
- Horizontal concatenation: Columns from all views appear sequentially
- Zero-copy: No data duplication, references original Views
- Automatic row padding: Shorter views treated as having nil cells for extra rows
- Preserves types: Each cell maintains its original type from source View
Use Cases ¶
- Joining related data: Combine user profiles with their statistics
- Adding computed columns: Append calculated values to existing data
- Merging partial data: Combine views from different data sources
- Building composite reports: Unite data from multiple queries
Row Count Behavior ¶
The resulting view has as many rows as the longest input View. Shorter views are implicitly padded with nil values:
View1: 10 rows, 2 columns View2: 5 rows, 3 columns ExtraColsView: 10 rows, 5 columns (View2 rows 5-9 are all nil)
Column Order ¶
Columns appear in the order of views in the slice:
ExtraColsView{view1, view2, view3}
Columns: [view1.col0, view1.col1, view2.col0, view2.col1, view3.col0]
Performance Characteristics ¶
- No data copying: Only stores View references
- Linear column lookup: O(n) where n is number of views
- Constant row lookup: O(1) after finding the right view
- Memory efficient: Only overhead is the slice of View references
Example: Basic Column Concatenation ¶
// View1: Name and Age
people := NewStringsView("People", [][]string{
{"Alice", "30"},
{"Bob", "25"},
}, []string{"Name", "Age"})
// View2: City and Country
locations := NewStringsView("", [][]string{
{"NYC", "USA"},
{"London", "UK"},
}, []string{"City", "Country"})
// Combine them
combined := ExtraColsView{people, locations}
// Columns: ["Name", "Age", "City", "Country"]
// Row 0: ["Alice", "30", "NYC", "USA"]
// Row 1: ["Bob", "25", "London", "UK"]
Example: Unequal Row Counts ¶
view1 := NewStringsView("", [][]string{
{"A1"}, {"A2"}, {"A3"},
}, []string{"Col1"})
view2 := NewStringsView("", [][]string{
{"B1"},
}, []string{"Col2"})
combined := ExtraColsView{view1, view2}
// 3 rows total (max of 3 and 1)
combined.Cell(0, 0) -> "A1"
combined.Cell(0, 1) -> "B1"
combined.Cell(1, 0) -> "A2"
combined.Cell(1, 1) -> nil // view2 has no row 1
combined.Cell(2, 0) -> "A3"
combined.Cell(2, 1) -> nil // view2 has no row 2
Example: Adding Computed Columns ¶
// Original data
baseView := NewStructRowsView("Sales", salesData, nil, nil)
// Add computed columns via a custom view
computedView := ExtraColsAnyValueFuncView(nil, []string{"Total", "Tax"},
func(row, col int) any {
// Calculate based on baseView data
if col == 0 { return calculateTotal(row) }
return calculateTax(row)
})
// Combine
enriched := ExtraColsView{baseView, computedView}
// Now has original columns plus Total and Tax
Example: Multi-Source Join ¶
users := loadUsersView() // id, name, email
profiles := loadProfilesView() // bio, avatar
stats := loadStatsView() // post_count, follower_count
fullProfile := ExtraColsView{users, profiles, stats}
// Columns: id, name, email, bio, avatar, post_count, follower_count
Edge Cases ¶
- Empty ExtraColsView{} results in 0 rows, 0 columns, empty title
- Single view ExtraColsView{view} behaves identically to view
- Views with 0 rows contribute 0 to final row count
- Negative row/col indices return nil
- Column index beyond total column count returns nil
Composition ¶
ExtraColsView can be nested and combined with other decorators:
base := NewStringsView(...)
extra1 := ExtraColsView{base, computedView1}
extra2 := ExtraColsView{extra1, computedView2}
filtered := &FilteredView{Source: extra2, RowLimit: 10}
// First 10 rows with all computed columns
Title Behavior ¶
The title is taken from the first view in the slice. If you need a custom title, wrap the result with ViewWithTitle.
func (ExtraColsView) Cell ¶
func (e ExtraColsView) Cell(row, col int) any
Cell returns the value at the specified row and column position.
The column parameter is translated to the appropriate source view:
- Iterates through views to find which one contains the requested column
- Translates col to the local column index within that view
- Returns view.Cell(row, localCol)
Returns nil if:
- row or col are negative
- col >= total number of columns
- row >= NumRows() of the responsible view (implicit nil padding)
- The underlying view cell is nil
Example:
view1 has columns ["A", "B"] (indices 0-1)
view2 has columns ["C", "D", "E"] (indices 2-4 in combined view)
combined := ExtraColsView{view1, view2}
combined.Cell(0, 0) -> view1.Cell(0, 0) // Column "A"
combined.Cell(0, 1) -> view1.Cell(0, 1) // Column "B"
combined.Cell(0, 2) -> view2.Cell(0, 0) // Column "C"
combined.Cell(0, 3) -> view2.Cell(0, 1) // Column "D"
combined.Cell(0, 4) -> view2.Cell(0, 2) // Column "E"
combined.Cell(0, 5) -> nil // Out of bounds
Performance: O(n) where n is the number of views, as it must iterate to find the correct view. For performance-critical code with many views, consider caching column offsets.
func (ExtraColsView) Columns ¶
func (e ExtraColsView) Columns() []string
Columns returns all column names from all Views concatenated in order.
The resulting slice contains columns from each view sequentially:
[view0.col0, view0.col1, ..., view1.col0, view1.col1, ..., viewN.colM]
Returns an empty slice if ExtraColsView is empty.
Example:
view1.Columns() -> ["A", "B"]
view2.Columns() -> ["C", "D", "E"]
ExtraColsView{view1, view2}.Columns() -> ["A", "B", "C", "D", "E"]
func (ExtraColsView) NumRows ¶
func (e ExtraColsView) NumRows() int
NumRows returns the maximum row count across all Views.
The combined view has as many rows as its longest component View. Shorter views are implicitly treated as having nil cells for additional rows.
Returns 0 if ExtraColsView is empty or all views have 0 rows.
Example:
view1.NumRows() -> 10
view2.NumRows() -> 5
view3.NumRows() -> 15
ExtraColsView{view1, view2, view3}.NumRows() -> 15
func (ExtraColsView) Title ¶
func (e ExtraColsView) Title() string
Title returns the title of the first View in the slice. Returns an empty string if the slice is empty.
The title is not a combination of all view titles - only the first is used. To set a custom title, use ViewWithTitle to wrap the ExtraColsView.
type ExtraRowView ¶
type ExtraRowView []View
ExtraRowView is a zero-copy decorator that vertically concatenates multiple Views, stacking their rows into a single unified View. This is the row equivalent of a SQL UNION operation, allowing you to combine data from different sources with the same column structure.
Key Features ¶
- Vertical concatenation: Rows from all views appear sequentially
- Zero-copy: No data duplication, references original Views
- Column alignment: All views must have compatible column structures
- Preserves types: Each cell maintains its original type from source View
Use Cases ¶
- Combining result sets: Merge data from multiple queries
- Appending batches: Stack incrementally loaded data
- Unifying data sources: Combine test + prod data for reports
- Building composite datasets: Unite similar data from different time periods
Column Structure Requirements ¶
ExtraRowView uses the column structure from the first view in the slice. All subsequent views should have the same column count and compatible types. The implementation does NOT validate column compatibility - it's the caller's responsibility to ensure views have matching structures.
Column names are taken only from the first view:
View1: ["Name", "Age"] <- columns used View2: ["Name", "Age"] ExtraRowView columns: ["Name", "Age"]
If views have different columns, cells are accessed by position only:
View1: ["A", "B"] View2: ["X", "Y"] ExtraRowView: columns ["A", "B"], but view2.Cell(0, 0) returns "X" data
Row Count Behavior ¶
The resulting view has the sum of all input Views' row counts:
View1: 10 rows View2: 5 rows View3: 15 rows ExtraRowView: 30 rows total
Performance Characteristics ¶
- No data copying: Only stores View references
- Linear row lookup: O(n) where n is number of views
- Constant column lookup: O(1)
- Memory efficient: Only overhead is the slice of View references
Example: Basic Row Concatenation ¶
// Historical data
past := NewStringsView("", [][]string{
{"Alice", "30"},
{"Bob", "25"},
}, []string{"Name", "Age"})
// Recent data
recent := NewStringsView("", [][]string{
{"Charlie", "35"},
{"Diana", "28"},
}, []string{"Name", "Age"})
// Combine them
all := ExtraRowView{past, recent}
// 4 rows total:
// Row 0: ["Alice", "30"] (from past)
// Row 1: ["Bob", "25"] (from past)
// Row 2: ["Charlie", "35"] (from recent)
// Row 3: ["Diana", "28"] (from recent)
Example: Combining Multiple Data Sources ¶
usData := loadUSCustomers() // 100 rows
euData := loadEUCustomers() // 75 rows
asiaData := loadAsiaCustomers() // 50 rows
allCustomers := ExtraRowView{usData, euData, asiaData}
// 225 rows total, all with same columns
Example: Appending Summary Rows ¶
dataRows := loadDataView()
// Create a summary row view
summaryData := [][]any{
{"TOTAL", sumValues(), avgValues()},
}
summary := NewAnyValuesView("", summaryData, dataRows.Columns())
// Append summary at the bottom
withSummary := ExtraRowView{dataRows, summary}
// Last row contains the totals
Example: Combining Filtered Subsets ¶
allData := loadDataView()
// Split into categories
categoryA := &FilteredView{
Source: allData,
RowOffset: 0,
RowLimit: 10,
}
categoryB := &FilteredView{
Source: allData,
RowOffset: 50,
RowLimit: 10,
}
// Recombine selected rows
selected := ExtraRowView{categoryA, categoryB}
// 20 rows: first 10 from categoryA, then 10 from categoryB
Example: Multi-Batch Loading ¶
var batches []View
for batch := range loadIncrementally() {
batches = append(batches, batch)
}
allData := ExtraRowView(batches)
// All batches combined into single view
Row Index Translation ¶
Cell access translates row indices to the appropriate source view:
view1 has 10 rows (indices 0-9)
view2 has 5 rows (indices 10-14 in combined view)
view3 has 8 rows (indices 15-22 in combined view)
combined := ExtraRowView{view1, view2, view3}
combined.Cell(5, 0) -> view1.Cell(5, 0) // Within view1
combined.Cell(12, 0) -> view2.Cell(2, 0) // Within view2 (12-10=2)
combined.Cell(20, 0) -> view3.Cell(5, 0) // Within view3 (20-15=5)
Edge Cases ¶
- Empty ExtraRowView{} results in 0 rows, 0 columns, empty title
- Single view ExtraRowView{view} behaves identically to view
- Views with 0 rows contribute nothing to final result
- Negative row/col indices return nil
- Column index beyond first view's column count returns nil
- Row index beyond total row count returns nil
Column Mismatch Behavior ¶
If views have different column counts, no error occurs:
Columns() returns the first view's columns only
Accessing a column index >= a view's column count returns nil for that view's rows
view1 := NewStringsView("", [][]string{{"A", "B"}}, []string{"Col1", "Col2"}) view2 := NewStringsView("", [][]string{{"X"}}, []string{"Col1"})
combined := ExtraRowView{view1, view2} combined.Columns() -> ["Col1", "Col2"] combined.Cell(0, 0) -> "A" combined.Cell(0, 1) -> "B" combined.Cell(1, 0) -> "X" combined.Cell(1, 1) -> nil // view2 has no column 1
Composition ¶
ExtraRowView can be nested and combined with other decorators:
batch1 := loadBatch1()
batch2 := loadBatch2()
combined := ExtraRowView{batch1, batch2}
// Filter the combined result
filtered := &FilteredView{
Source: combined,
RowLimit: 100,
}
// Add computed columns to the combination
enriched := ExtraColsAnyValueFuncView(filtered, []string{"Score"}, scoreFunc)
Title Behavior ¶
The title is taken from the first view in the slice. If you need a custom title, wrap the result with ViewWithTitle.
func (ExtraRowView) Cell ¶
func (e ExtraRowView) Cell(row, col int) any
Cell returns the value at the specified row and column position.
The row parameter is translated to the appropriate source view:
- Iterates through views to find which one contains the requested row
- Translates row to the local row index within that view
- Returns view.Cell(localRow, col)
Returns nil if:
- row or col are negative
- row >= total number of rows
- col >= number of columns (from first view)
- The underlying view cell is nil
Example:
view1 has 2 rows (indices 0-1 in combined view)
view2 has 3 rows (indices 2-4 in combined view)
combined := ExtraRowView{view1, view2}
combined.Cell(0, 0) -> view1.Cell(0, 0) // First row of view1
combined.Cell(1, 0) -> view1.Cell(1, 0) // Second row of view1
combined.Cell(2, 0) -> view2.Cell(0, 0) // First row of view2
combined.Cell(3, 0) -> view2.Cell(1, 0) // Second row of view2
combined.Cell(4, 0) -> view2.Cell(2, 0) // Third row of view2
combined.Cell(5, 0) -> nil // Out of bounds
Performance: O(n) where n is the number of views, as it must iterate to find the correct view. For performance-critical code with many views, consider pre-computing row offsets.
func (ExtraRowView) Columns ¶
func (e ExtraRowView) Columns() []string
Columns returns the column names from the first View in the slice. Returns nil if the slice is empty.
All views in ExtraRowView should have the same column structure, but this is not enforced. Only the first view's columns are used to define the structure of the combined view.
Example:
view1.Columns() -> ["A", "B", "C"]
view2.Columns() -> ["A", "B", "C"]
ExtraRowView{view1, view2}.Columns() -> ["A", "B", "C"]
func (ExtraRowView) NumRows ¶
func (e ExtraRowView) NumRows() int
NumRows returns the total row count across all Views.
The combined view has the sum of row counts from all component Views. Views with 0 rows contribute nothing to the total.
Returns 0 if ExtraRowView is empty or all views have 0 rows.
Example:
view1.NumRows() -> 10
view2.NumRows() -> 5
view3.NumRows() -> 15
ExtraRowView{view1, view2, view3}.NumRows() -> 30
func (ExtraRowView) Title ¶
func (e ExtraRowView) Title() string
Title returns the title of the first View in the slice. Returns an empty string if the slice is empty.
The title is not a combination of all view titles - only the first is used. To set a custom title, use ViewWithTitle to wrap the ExtraRowView.
type FilteredView ¶
type FilteredView struct {
// Source is the underlying View to filter and transform.
// All Cell() calls are delegated to this View after applying
// row offset and column mapping transformations.
Source View
// RowOffset is the index of the first row from Source to include.
// Rows 0 to RowOffset-1 are skipped.
// Negative values are treated as 0.
// If RowOffset >= Source.NumRows(), the view will have 0 rows.
//
// Example:
// RowOffset: 0 -> starts at first row (no offset)
// RowOffset: 10 -> skips first 10 rows, starts at row 10
RowOffset int
// RowLimit caps the maximum number of rows in this view.
// If RowLimit is 0 or negative, no limit is applied (all rows shown).
// If RowLimit > 0, at most RowLimit rows are shown.
// The actual row count may be less if Source has fewer rows available.
//
// Example:
// RowLimit: 0 -> no limit, show all rows after RowOffset
// RowLimit: 10 -> show at most 10 rows
RowLimit int
// ColumnMapping controls which columns are visible and in what order.
//
// If nil:
// - All columns from Source are included
// - Columns appear in their original order
// - NumCols() returns len(Source.Columns())
//
// If not nil:
// - Only mapped columns are included
// - len(ColumnMapping) determines NumCols()
// - Each element is an index into Source's columns
// - Columns can be reordered or duplicated
//
// Example:
// ColumnMapping: nil -> [0, 1, 2, 3] (all columns)
// ColumnMapping: []int{0} -> [0] (only first column)
// ColumnMapping: []int{2,0} -> [2, 0] (columns 2 and 0, reordered)
// ColumnMapping: []int{1,1} -> [1, 1] (column 1 duplicated)
//
// WARNING: Invalid indices (negative or >= Source.NumCols()) will cause
// panics when accessing cells or column names.
ColumnMapping []int
}
FilteredView is a decorator that provides zero-copy filtering, slicing, and column remapping of an underlying View. It implements pagination, column selection, and column reordering without duplicating the underlying data.
Key Features ¶
- Row slicing: Skip rows (RowOffset) and limit results (RowLimit)
- Column filtering: Select specific columns via ColumnMapping
- Column reordering: Rearrange columns by mapping indices
- Zero-copy: No data duplication, just index manipulation
Use Cases ¶
- Pagination: Implement offset/limit patterns for large datasets
- Column selection: Export only specific columns (e.g., for CSV or API responses)
- Column reordering: Change column display order without modifying source data
- Data projection: Create different views of the same underlying data
Performance Characteristics ¶
FilteredView is extremely lightweight:
- No data copying: Source data is never duplicated
- Constant overhead: Cell access adds only index arithmetic
- Memory efficient: Only stores integers for offset, limit, and mapping
Example: Pagination ¶
// Original view with 1000 rows
source := NewStringsView("Products", productData, columns)
// Get page 3 (rows 20-29, assuming page size of 10)
page3 := &FilteredView{
Source: source,
RowOffset: 20,
RowLimit: 10,
}
fmt.Println(page3.NumRows()) // 10
Example: Column Selection ¶
// Select only columns 0, 2, and 4 from source
filtered := &FilteredView{
Source: source,
ColumnMapping: []int{0, 2, 4},
}
// Accessing column 0 of filtered reads column 0 of source
// Accessing column 1 of filtered reads column 2 of source
// Accessing column 2 of filtered reads column 4 of source
Example: Column Reordering ¶
// Reverse column order (assuming 3 columns)
reversed := &FilteredView{
Source: source,
ColumnMapping: []int{2, 1, 0},
}
Example: Combined Operations ¶
// Select specific columns AND paginate
view := &FilteredView{
Source: source,
RowOffset: 100,
RowLimit: 25,
ColumnMapping: []int{0, 3, 5}, // Only columns 0, 3, 5
}
Field Details ¶
- Source: The underlying View to filter/transform (required)
- RowOffset: Number of rows to skip from the beginning (0 = no offset)
- RowLimit: Maximum number of rows to include (0 = no limit, show all remaining)
- ColumnMapping: nil = all columns; []int = column indices to include/reorder
Edge Cases ¶
- Negative RowOffset is treated as 0
- RowOffset beyond source length results in 0 rows
- Out-of-bounds cell access returns nil
- Invalid column mapping indices will panic on Cell() access
Composition ¶
FilteredView can wrap other FilteredViews for complex transformations:
base := NewStringsView(...)
step1 := &FilteredView{Source: base, ColumnMapping: []int{0, 2, 4, 6}}
step2 := &FilteredView{Source: step1, RowOffset: 10, RowLimit: 5}
// step2 shows rows 10-14 of base, with columns 0, 2, 4, 6
func (*FilteredView) Cell ¶
func (view *FilteredView) Cell(row, col int) any
Cell returns the value at the specified row and column position in this filtered view.
The row and col parameters are relative to this view's coordinate space (after filtering), not the Source view's coordinates.
Returns nil if:
- row or col are negative
- row >= NumRows()
- col >= NumCols()
- The underlying Source cell is nil
The method translates coordinates before accessing Source:
- Row: adds RowOffset to translate to Source's row space
- Col: maps through ColumnMapping if set, otherwise uses col directly
Example:
source := NewStringsView("Data", [][]string{
{"A0", "B0", "C0"}, // row 0
{"A1", "B1", "C1"}, // row 1
{"A2", "B2", "C2"}, // row 2
{"A3", "B3", "C3"}, // row 3
}, []string{"A", "B", "C"})
filtered := &FilteredView{
Source: source,
RowOffset: 1, // skip first row
RowLimit: 2, // show 2 rows
ColumnMapping: []int{2, 0}, // columns C, A
}
filtered.Cell(0, 0) -> "C1" (row 1, col 2 in source)
filtered.Cell(0, 1) -> "A1" (row 1, col 0 in source)
filtered.Cell(1, 0) -> "C2" (row 2, col 2 in source)
filtered.Cell(2, 0) -> nil (row 2 is out of bounds in filtered view)
func (*FilteredView) Columns ¶
func (view *FilteredView) Columns() []string
Columns returns the column names for this filtered view.
If ColumnMapping is nil, returns all columns from Source in their original order. If ColumnMapping is set, returns only the mapped columns in the specified order.
The returned slice is newly allocated and safe to modify.
Example:
source.Columns() -> ["A", "B", "C", "D"]
view := &FilteredView{Source: source, ColumnMapping: []int{3, 1}}
view.Columns() -> ["D", "B"]
func (*FilteredView) NumCols ¶
func (view *FilteredView) NumCols() int
NumCols returns the number of columns in this filtered view.
If ColumnMapping is nil, returns the number of columns in Source. If ColumnMapping is set, returns len(ColumnMapping).
This method is more efficient than len(view.Columns()) as it avoids allocating the column name slice.
func (*FilteredView) NumRows ¶
func (view *FilteredView) NumRows() int
NumRows returns the number of rows visible in this filtered view after applying RowOffset and RowLimit.
The calculation:
- Start with Source.NumRows()
- Subtract RowOffset (treated as 0 if negative)
- Apply RowLimit if > 0
- Return 0 if result would be negative
Examples:
Source has 100 rows: RowOffset: 0, RowLimit: 0 -> 100 rows RowOffset: 10, RowLimit: 0 -> 90 rows RowOffset: 10, RowLimit: 20 -> 20 rows RowOffset: 95, RowLimit: 20 -> 5 rows (limited by available data) RowOffset: 200, RowLimit: 0 -> 0 rows (offset beyond data)
func (*FilteredView) Title ¶
func (view *FilteredView) Title() string
Title returns the title from the underlying Source view. FilteredView does not modify the title.
type Formatter ¶
type Formatter interface {
// Format converts a reflect.Value to its string representation.
// Returns errors.ErrUnsupported if the formatter doesn't support the value's type.
Format(reflect.Value) (string, error)
}
Formatter is a reflection-based value formatter that converts a reflect.Value to a string. This is the simpler, lower-level interface compared to CellFormatter, as it operates directly on reflect.Value without requiring a View context or cell coordinates.
Formatter is useful when you need to format individual values that aren't necessarily part of a table structure, or when building custom CellFormatter implementations.
Example usage:
formatter := FormatterFunc(func(v reflect.Value) (string, error) {
if v.Kind() == reflect.Int {
return fmt.Sprintf("#%d", v.Int()), nil
}
return "", errors.ErrUnsupported
})
str, err := formatter.Format(reflect.ValueOf(42))
// str == "#42"
func FormatterFromCellFormatter ¶
func FormatterFromCellFormatter(f CellFormatter) Formatter
FormatterFromCellFormatter adapts a CellFormatter to work as a simple Formatter. This is the reverse bridge of CellFormatterFromFormatter, allowing table cell formatters to be used for formatting individual reflect.Values.
The adapter creates a minimal single-value View containing just the provided reflect.Value at position (0, 0) and calls the CellFormatter on it. The context used is context.Background() since there's no external context available.
The "raw" result from the CellFormatter is discarded, as the simpler Formatter interface doesn't have this concept.
Parameters:
- f: The CellFormatter to adapt
Returns:
- A Formatter that delegates to the provided CellFormatter
Example:
cellFormatter := PrintfCellFormatter("%d%%")
formatter := FormatterFromCellFormatter(cellFormatter)
str, _ := formatter.Format(reflect.ValueOf(95))
// str == "95%"
type FormatterFunc ¶
FormatterFunc is a function type that implements the Formatter interface, allowing plain functions to be used as Formatters.
This adapter type follows the common Go pattern of defining a function type that implements an interface (similar to http.HandlerFunc).
Example:
var formatter Formatter = FormatterFunc(func(v reflect.Value) (string, error) {
return fmt.Sprintf("value: %v", v.Interface()), nil
})
type HeaderView ¶
type HeaderView struct {
// Tit is the title of this view.
Tit string
// Cols contains the column names, which are also used as the data row.
Cols []string
}
HeaderView is a specialized View that contains only a header row.
This view type displays column names as both the column headers and the single data row, making it useful for showing just table structure or for combining multiple views where you need header information displayed.
The view always has exactly one row (NumRows() returns 1), and that row contains the column names as values.
When to use HeaderView:
- Displaying table structure/schema information
- Creating view combinations where headers need to be visible
- Testing or debugging column configurations
- Generating table metadata displays
Example:
view := &retable.HeaderView{
Tit: "User Schema",
Cols: []string{"ID", "Name", "Email", "Active"},
}
// Row 0 will contain: "ID", "Name", "Email", "Active"
func NewHeaderView ¶
func NewHeaderView(cols ...string) *HeaderView
NewHeaderView creates a View containing only a header row.
The header row displays the column names as data, creating a single-row view where the column names are also the cell values in that row. This is useful for displaying just the header information or combining with other views.
All column names have leading and trailing whitespace trimmed automatically.
Parameters:
- cols: Variable number of column names
Returns a new HeaderView with one row containing the column names.
Example:
view := retable.NewHeaderView("ID", "Name", "Email")
fmt.Println(view.NumRows()) // Output: 1
fmt.Println(view.Cell(0, 1)) // Output: Name
func NewHeaderViewFrom ¶
func NewHeaderViewFrom(source View) *HeaderView
NewHeaderViewFrom creates a HeaderView from an existing View's columns.
This function extracts the column names and title from the source View and creates a new HeaderView where the column names appear as both the header and the single data row.
Parameters:
- source: The View to extract column names from
Returns a new HeaderView with the source's title and columns.
Example:
original := retable.NewStringsView("Products", data, "ID", "Name", "Price")
headerOnly := retable.NewHeaderViewFrom(original)
// headerOnly contains just one row with "ID", "Name", "Price" as values
func (*HeaderView) Cell ¶
func (view *HeaderView) Cell(row, col int) any
Cell returns the column name at the specified column index for row 0.
Since HeaderView has only one row, row must be 0. The returned value is the column name at the given column index.
Parameters:
- row: Must be 0, otherwise nil is returned
- col: Zero-based column index (0 to len(Columns())-1)
Returns:
- The column name as any if row is 0 and col is valid
- nil if row is not 0 or col is out of bounds
func (*HeaderView) Columns ¶
func (view *HeaderView) Columns() []string
Columns returns the column names of this view.
func (*HeaderView) NumRows ¶
func (view *HeaderView) NumRows() int
NumRows always returns 1 for HeaderView since it contains only the header row.
func (*HeaderView) Title ¶
func (view *HeaderView) Title() string
Title returns the title of this view.
type LayoutFormatter ¶
type LayoutFormatter string
LayoutFormatter formats values that implement the Format(string) string method, such as time.Time, by calling their Format method with the layout string.
This formatter is specifically designed for types like time.Time that use layout strings for formatting. The string value of this type becomes the layout parameter passed to the value's Format method.
If the cell value doesn't implement the required interface, an error is returned (not errors.ErrUnsupported, but a descriptive error about the type mismatch).
Example usage:
// Format time.Time values as ISO dates
formatter := LayoutFormatter("2006-01-02")
str, raw, err := formatter.FormatCell(ctx, view, 0, 0)
// For time.Time value of "2024-03-15 14:30:00"
// str == "2024-03-15", raw == false
// Format as 12-hour time
timeFormatter := LayoutFormatter("3:04 PM")
// For time.Time value of "2024-03-15 14:30:00"
// str == "2:30 PM"
func (LayoutFormatter) FormatCell ¶
func (f LayoutFormatter) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements CellFormatter by calling the cell value's Format method with the layout string. Returns an error if the value doesn't implement interface{ Format(string) string }.
type Option ¶
type Option int
Option represents formatting options that can be applied when rendering tables. Options are implemented as bit flags and can be combined using bitwise OR.
const ( // OptionAddHeaderRow adds a header row containing column titles // as the first row in the formatted output. // // When this option is set, the View's Columns() method is called to get column titles, // and these are rendered as the first row before any data rows. // // Example: // // rows, err := FormatViewAsStrings(ctx, view, nil, OptionAddHeaderRow) // // First row will contain: ["Column1", "Column2", "Column3"] // // Subsequent rows contain data OptionAddHeaderRow Option = 1 << iota )
func (Option) Has ¶
Has checks whether this Option has the specified option flag set. Since Option is a bit flag type, multiple options can be combined and this method tests if a specific option is present.
Example:
opts := OptionAddHeaderRow
if opts.Has(OptionAddHeaderRow) {
// Header row option is enabled
}
func (Option) String ¶
String returns a human-readable string representation of the Option. Multiple options are joined with "|". If no options are set, returns "no Option".
Example:
opt := OptionAddHeaderRow fmt.Println(opt.String()) // Output: "AddHeaderRow" opt = 0 fmt.Println(opt.String()) // Output: "no Option"
type Parser ¶
type Parser interface {
// ParseInt parses a string into a 64-bit signed integer.
// Returns an error if the string is not a valid integer.
ParseInt(string) (int64, error)
// ParseUint parses a string into a 64-bit unsigned integer.
// Returns an error if the string is not a valid unsigned integer.
ParseUint(string) (uint64, error)
// ParseFloat parses a string into a 64-bit floating point number.
// May handle locale-specific formatting (e.g., comma vs. period decimals).
// Returns an error if the string is not a valid float.
ParseFloat(string) (float64, error)
// ParseBool parses a string into a boolean value.
// The strings recognized as true/false are implementation-specific.
// Returns an error if the string is not recognized as a boolean.
ParseBool(string) (bool, error)
// ParseTime parses a string into a time.Time value.
// May try multiple time formats in sequence.
// Returns an error if the string doesn't match any recognized format.
ParseTime(string) (time.Time, error)
// ParseDuration parses a string into a time.Duration value.
// Uses Go's standard duration format (e.g., "1h30m", "5s").
// Returns an error if the string is not a valid duration.
ParseDuration(string) (time.Duration, error)
}
Parser is the interface for parsing string representations into primitive Go types. This is the counterpart to formatting - it handles the conversion from strings back to typed values.
Parser provides a centralized place to configure parsing behavior, including:
- Which strings represent boolean true/false values
- Which strings represent nil/null values
- Which time formats to try when parsing time values
- Locale-specific number formatting (e.g., comma vs. period decimal separators)
The Parser interface is used by Scanners to perform the actual string-to-value conversions. By abstracting parsing into an interface, different parsing strategies can be used (strict vs. lenient, different locale conventions, etc.).
Example usage:
parser := NewStringParser()
// Configure custom boolean strings
parser.TrueStrings = []string{"true", "yes", "1", "on"}
parser.FalseStrings = []string{"false", "no", "0", "off"}
// Parse various types
i, _ := parser.ParseInt("42") // 42
f, _ := parser.ParseFloat("3,14") // 3.14 (handles comma decimal)
b, _ := parser.ParseBool("yes") // true
t, _ := parser.ParseTime("2024-03-15") // time.Time value
type PrintfCellFormatter ¶
type PrintfCellFormatter string
PrintfCellFormatter implements CellFormatter by applying a Printf-style format string to cell values. The string value of this type is used as the format string for fmt.Sprintf.
This formatter never returns errors.ErrUnsupported - it will format any value type that fmt.Sprintf can handle, which includes all basic Go types. The formatted result is marked as non-raw (raw=false), meaning it should be sanitized for the output format.
Example usage:
// Format numbers with 2 decimal places
formatter := PrintfCellFormatter("%.2f")
str, raw, _ := formatter.FormatCell(ctx, view, 0, 0)
// For value 3.14159, str == "3.14", raw == false
// Format as percentage
percentFormatter := PrintfCellFormatter("%.0f%%")
str, _, _ := percentFormatter.FormatCell(ctx, view, 0, 0)
// For value 95, str == "95%"
func (PrintfCellFormatter) FormatCell ¶
func (format PrintfCellFormatter) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements CellFormatter using fmt.Sprintf with the format string. The formatted result is always marked as non-raw (requiring sanitization). Never returns an error.
type PrintfRawCellFormatter ¶
type PrintfRawCellFormatter string
PrintfRawCellFormatter implements CellFormatter by applying a Printf-style format string to cell values, similar to PrintfCellFormatter, but marks the result as raw output.
The key difference from PrintfCellFormatter is that this formatter marks its output as "raw" (raw=true), indicating the formatted string is already in the correct format for the target output and doesn't need sanitization. This is useful when the format string produces output that's already escaped or contains format-specific markup.
Example usage:
// Format as HTML with embedded tags (already properly escaped)
formatter := PrintfRawCellFormatter("<b>%s</b>")
str, raw, _ := formatter.FormatCell(ctx, view, 0, 0)
// For value "Title", str == "<b>Title</b>", raw == true
// The raw=true signals that no further HTML escaping is needed
func (PrintfRawCellFormatter) FormatCell ¶
func (format PrintfRawCellFormatter) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements CellFormatter using fmt.Sprintf with the format string. The formatted result is always marked as raw (not requiring sanitization). Never returns an error.
type RawCellString ¶
type RawCellString string
RawCellString is a constant string CellFormatter that ignores the cell value entirely and always returns its own string value, marked as raw output.
This formatter is useful for injecting fixed content (like HTML snippets, icons, or format-specific markup) into table cells regardless of the actual cell value. Since the output is marked as raw, it won't be sanitized by the output format.
Example usage:
// Always show an HTML checkbox, regardless of cell value
checkboxFormatter := RawCellString(`<input type="checkbox" checked>`)
str, raw, _ := checkboxFormatter.FormatCell(ctx, view, 0, 0)
// str == `<input type="checkbox" checked>`, raw == true
// Use as a constant marker
marker := RawCellString("✓")
func (RawCellString) FormatCell ¶
func (rawStr RawCellString) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements CellFormatter by returning the constant string as raw output. The cell value is ignored. Never returns an error.
type RawStringIfTrue ¶
type RawStringIfTrue string
RawStringIfTrue formats boolean cells by returning a specified string for true values and an empty string for false values, with the output marked as raw.
This is similar to StringIfTrue, but marks the output as raw, indicating it doesn't need sanitization. This is useful when the string contains format-specific markup like HTML tags or pre-escaped content. It panics if the cell value is not a bool.
Example usage:
// Show HTML icon for true, nothing for false formatter := RawStringIfTrue(`<span class="icon-check"></span>`) str, raw, _ := formatter.FormatCell(ctx, view, 0, 0) // If cell value is true: str == `<span class="icon-check"></span>`, raw == true // If cell value is false: str == "", raw == true
func (RawStringIfTrue) FormatCell ¶
func (f RawStringIfTrue) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements CellFormatter for boolean values with raw output. Returns the string value marked as raw for true, empty string for false. Panics if the cell value is not a bool. Never returns an error.
type ReflectCellView ¶
type ReflectCellView interface {
View
// ReflectCell returns the reflect.Value of the cell at the specified position.
// This provides direct access to the underlying value's reflection metadata.
//
// The returned reflect.Value can be:
// - A valid value with the cell's actual type and value
// - A zero Value (from reflect.Value{}) if row/col are out of bounds
// - A reflect.Value containing nil if the cell is nil
//
// To check if a returned Value is valid, use:
// val := view.ReflectCell(row, col)
// if val.IsValid() { /* use val */ }
//
// Row and column indices are zero-based, same as Cell().
ReflectCell(row, col int) reflect.Value
}
ReflectCellView extends the View interface with reflection-based cell access. This interface is useful when working with generic code that needs to inspect or manipulate cell values using Go's reflect package.
Use Cases ¶
- Type-safe data conversion via SmartAssign
- Generic formatters that inspect value types
- Building dynamic struct scanners
- Implementing custom CellFormatter instances
Automatic Wrapping ¶
Not all Views natively implement ReflectCellView. Use AsReflectCellView() to get a ReflectCellView from any View - it will either return the View directly (if it implements the interface) or wrap it automatically.
Performance Consideration ¶
Views that natively implement ReflectCellView (like ReflectValuesView) are more efficient than wrapped views, as they avoid repeated reflection operations via reflect.ValueOf().
Example Usage ¶
view := NewStringsView("Data", [][]string{{"123", "true"}}, []string{"Number", "Flag"})
reflectView := AsReflectCellView(view)
val := reflectView.ReflectCell(0, 0) // reflect.Value containing "123"
if val.Kind() == reflect.String {
fmt.Println(val.String()) // "123"
}
func AsReflectCellView ¶
func AsReflectCellView(view View) ReflectCellView
AsReflectCellView converts any View to a ReflectCellView.
If the input View already implements ReflectCellView, it is returned as-is with no allocation. Otherwise, the View is wrapped in a lightweight adapter that implements ReflectCellView by calling reflect.ValueOf() on Cell() results.
This function never returns nil - it always returns a valid ReflectCellView.
Example Usage ¶
var view View = NewStringsView(...) reflectView := AsReflectCellView(view) // reflectView.ReflectCell() is now available
Performance Note ¶
The wrapper created for non-ReflectCellView inputs is very lightweight (just a struct embedding the original View), but calling ReflectCell() will perform reflection on each call. For performance-critical code with heavy reflection usage, prefer Views that natively implement ReflectCellView.
func DerefView ¶
func DerefView(source View) ReflectCellView
DerefView creates a zero-copy decorator that automatically dereferences pointer values in a source View. This is particularly useful when working with Views of pointer types (e.g., []*Person) but you need to access the underlying values.
Use Cases ¶
- Converting Views of pointer slices ([]*T) to value access
- Unwrapping optional/nullable values represented as pointers
- Simplifying code that works with dereferenced values
- Preparing data for formatters that expect concrete values
How It Works ¶
DerefView wraps any View and calls reflect.Value.Elem() on every cell value, which dereferences pointers, interfaces, and other indirect types. The source View is automatically converted to ReflectCellView for efficient operation.
Performance ¶
- Zero data copying: Only dereferences on access
- Reflection overhead: Each Cell() call performs reflection
- Efficient for sparse access: Only dereferences accessed cells
Panics ¶
DerefView will panic if:
- A cell value cannot be dereferenced (e.g., non-pointer primitive)
- A cell contains a nil pointer and you try to access it
- The reflect.Value.Elem() operation is invalid for the value type
Example: Dereferencing Pointer Structs ¶
type Person struct {
Name string
Age int
}
// Source with pointer values
people := []*Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
source := NewStructRowsView("People", people, nil, nil)
// source.Cell(0, 0) returns &Person{Name: "Alice", Age: 30}
// Deref view automatically unwraps pointers
deref := DerefView(source)
// deref.Cell(0, 0) returns Person{Name: "Alice", Age: 30}
Example: Unwrapping Interface Values ¶
// View with cells wrapped in interface{}
source := NewAnyValuesView("Data", [][]any{
{any(&Person{Name: "Alice"})},
})
deref := DerefView(source)
// Dereferences the pointer inside the interface
deref.Cell(0, 0) // Returns Person{Name: "Alice"}
Example: Composition with Other Decorators ¶
// Combine dereferencing with filtering
source := NewStructRowsView("People", peoplePointers, nil, nil)
deref := DerefView(source)
filtered := &FilteredView{
Source: deref,
RowLimit: 10,
}
// filtered provides first 10 dereferenced values
Safety Considerations ¶
To safely use DerefView with potentially nil pointers, check values first:
val := source.Cell(row, col)
if val != nil {
derefVal := deref.Cell(row, col)
// Use derefVal
}
Or use reflection to check before dereferencing:
rv := deref.(ReflectCellView).ReflectCell(row, col)
if rv.IsValid() && !rv.IsNil() {
// Safe to use rv.Interface()
}
Return Type ¶
Returns a ReflectCellView (which embeds View), providing both Cell() and ReflectCell() methods for flexibility in value access.
func ExtraColsAnyValueFuncView ¶
func ExtraColsAnyValueFuncView(left View, columns []string, anyValue func(row, col int) any) ReflectCellView
ExtraColsAnyValueFuncView creates a View that appends dynamically computed columns to a base View. The additional column values are generated on-demand by a user-provided function, enabling lazy evaluation and computed fields without data duplication.
Use Cases ¶
- Adding calculated fields: totals, percentages, derived metrics
- Generating display columns: formatted strings, URLs, labels
- Implementing virtual columns: values computed from other columns
- Creating composite views: enrich data with external lookups
How It Works ¶
The function receives relative column indices (0-based within the added columns) and row indices, returning the computed value for that position. The left View provides the base columns, and the function provides the extra columns on the right.
Performance ¶
- Zero base data copying: Left view data is never duplicated
- Lazy evaluation: Values computed only when accessed
- Function overhead: Each Cell() call invokes the function
- Efficient for sparse access: Only computes accessed cells
Parameters ¶
- left: The base View to extend (can be nil for a pure computed view)
- columns: Names for the computed columns (determines column count)
- anyValue: Function that computes cell values (row, col) -> any
Column Indices ¶
The col parameter passed to anyValue is 0-based within the new columns only:
left has 3 columns: ["A", "B", "C"] Adding 2 columns: ["D", "E"] Combined view columns: ["A", "B", "C", "D", "E"] Cell(0, 3) -> anyValue(0, 0) // col 3 is "D", passed as 0 to anyValue Cell(0, 4) -> anyValue(0, 1) // col 4 is "E", passed as 1 to anyValue
Example: Adding Calculated Columns ¶
// Base data: product prices
products := NewStringsView("Products", [][]string{
{"Widget", "100"},
{"Gadget", "200"},
}, []string{"Name", "Price"})
// Add computed tax and total columns
withTax := ExtraColsAnyValueFuncView(products, []string{"Tax", "Total"},
func(row, col int) any {
priceStr := products.Cell(row, 1).(string)
price, _ := strconv.ParseFloat(priceStr, 64)
if col == 0 { // Tax column
return price * 0.1
}
// Total column
return price * 1.1
})
// Result columns: ["Name", "Price", "Tax", "Total"]
// Row 0: ["Widget", "100", 10.0, 110.0]
// Row 1: ["Gadget", "200", 20.0, 220.0]
Example: Pure Computed View (No Left View) ¶
// Generate a multiplication table without any base data
table := ExtraColsAnyValueFuncView(nil, []string{"A", "B", "Product"},
func(row, col int) any {
if col == 0 {
return row + 1
}
if col == 1 {
return row + 2
}
return (row + 1) * (row + 2)
})
// When NumRows() is called, returns 0 because left is nil
// Typically you'd wrap this in a FilteredView to set row count
Example: Lookups and External Data ¶
userData := loadUserView() // id, name
withStatus := ExtraColsAnyValueFuncView(userData, []string{"Status", "LastSeen"},
func(row, col int) any {
userID := userData.Cell(row, 0).(int)
if col == 0 {
return statusCache.Get(userID)
}
return lastSeenCache.Get(userID)
})
Example: Conditional Formatting ¶
scores := loadScoresView() // name, score
withGrade := ExtraColsAnyValueFuncView(scores, []string{"Grade"},
func(row, col int) any {
score := scores.Cell(row, 1).(int)
if score >= 90 { return "A" }
if score >= 80 { return "B" }
if score >= 70 { return "C" }
return "F"
})
Edge Cases ¶
- If left is nil, NumRows() returns 0 (function never called)
- Empty columns slice is valid (adds no columns)
- Function can return nil for any cell
- Out-of-bounds access returns nil (function not called)
Composition ¶
Can be chained to add multiple sets of computed columns:
base := loadDataView()
step1 := ExtraColsAnyValueFuncView(base, []string{"A"}, funcA)
step2 := ExtraColsAnyValueFuncView(step1, []string{"B", "C"}, funcBC)
// step2 has base columns plus A, B, C
Return Type ¶
Returns a ReflectCellView, providing both Cell() and ReflectCell() access. This enables efficient integration with reflection-based operations.
func ExtraColsReflectValueFuncView ¶
func ExtraColsReflectValueFuncView(left View, columns []string, reflectValue func(row, col int) reflect.Value) ReflectCellView
ExtraColsReflectValueFuncView creates a View that appends dynamically computed columns using a reflection-based value function. This is similar to ExtraColsAnyValueFuncView but provides reflect.Value access for efficiency when working with reflection.
When To Use ¶
Use this instead of ExtraColsAnyValueFuncView when:
- You're already working with reflect.Value in your code
- You need to avoid boxing/unboxing via Interface()
- You want to return invalid reflect.Value to represent nil
- Performance-critical reflection operations
Key Difference ¶
The function receives and returns reflect.Value instead of any:
- More efficient: Avoids Interface() conversions
- More control: Can return invalid reflect.Values for nil
- More complex: Requires reflection knowledge
Parameters ¶
- left: The base View to extend (can be nil)
- columns: Names for the computed columns
- reflectValue: Function that computes cell values (row, col) -> reflect.Value
Example: Efficient Reflection-Based Computation ¶
type Product struct {
Name string
Price float64
}
products := NewStructRowsView("Products", productSlice, nil, nil)
withFormatted := ExtraColsReflectValueFuncView(products, []string{"FormattedPrice"},
func(row, col int) reflect.Value {
rv := products.(ReflectCellView).ReflectCell(row, 1) // Price field
price := rv.Float()
formatted := fmt.Sprintf("$%.2f", price)
return reflect.ValueOf(formatted)
})
Example: Returning nil as Invalid Value ¶
withOptional := ExtraColsReflectValueFuncView(base, []string{"Optional"},
func(row, col int) reflect.Value {
if shouldBeNil(row) {
return reflect.Value{} // Invalid value represents nil
}
return reflect.ValueOf(computeValue(row))
})
Example: Type Conversion with Reflection ¶
withConverted := ExtraColsReflectValueFuncView(base, []string{"AsString"},
func(row, col int) reflect.Value {
val := base.(ReflectCellView).ReflectCell(row, 0)
// Use reflection to convert to string
str := fmt.Sprint(val.Interface())
return reflect.ValueOf(str)
})
Invalid Reflect Values ¶
If reflectValue returns an invalid reflect.Value (reflect.Value{}), the Cell() method will return nil. This is the recommended way to represent null/missing values.
Performance Advantages ¶
When implementing complex reflection-based transformations:
// Less efficient: Using ExtraColsAnyValueFuncView
ExtraColsAnyValueFuncView(v, cols, func(r, c int) any {
rv := v.(ReflectCellView).ReflectCell(r, c)
// ... reflection operations ...
return rv.Interface() // Extra boxing
})
// More efficient: Using ExtraColsReflectValueFuncView
ExtraColsReflectValueFuncView(v, cols, func(r, c int) reflect.Value {
rv := v.(ReflectCellView).ReflectCell(r, c)
// ... reflection operations ...
return rv // No boxing
})
Return Type ¶
Returns a ReflectCellView, providing efficient reflection-based access through both Cell() and ReflectCell() methods.
type ReflectTypeCellFormatter ¶
type ReflectTypeCellFormatter struct {
// Types maps exact reflect.Type to their CellFormatters.
// This has the highest priority in the matching hierarchy.
// Example: reflect.TypeOf(time.Time{}) -> LayoutFormatter("2006-01-02")
Types map[reflect.Type]CellFormatter
// InterfaceTypes maps interface types to their CellFormatters.
// Cell types are checked if they implement these interfaces.
// Example: reflect.TypeOf((*fmt.Stringer)(nil)).Elem() -> custom formatter
InterfaceTypes map[reflect.Type]CellFormatter
// Kinds maps reflect.Kind to their CellFormatters.
// This provides fallback formatting for broad categories of types.
// Example: reflect.Int -> PrintfCellFormatter("%d")
Kinds map[reflect.Kind]CellFormatter
// Default is the fallback formatter used when no type, interface, or kind matches.
// If nil and no match is found, errors.ErrUnsupported is returned.
Default CellFormatter
}
ReflectTypeCellFormatter is a sophisticated type-based routing formatter that selects the appropriate CellFormatter based on the reflected type, interface, or kind of a cell value.
This formatter implements a hierarchical matching strategy:
- Exact type match (Types map) - highest priority
- Interface type match (InterfaceTypes map) - checks if type implements interface
- Kind match (Kinds map) - matches reflect.Kind (int, string, struct, etc.)
- Pointer dereferencing - if pointer type has no match, checks dereferenced type
- Default formatter - fallback for unmatched types
The formatter returns errors.ErrUnsupported if no matching formatter is found and no Default is configured, allowing it to be used in formatter chains.
Design pattern: This type uses an immutable builder pattern where all With* methods return a new instance, allowing for safe concurrent usage and compositional configuration.
Example usage:
formatter := NewReflectTypeCellFormatter().
WithTypeFormatter(reflect.TypeOf(time.Time{}), LayoutFormatter("2006-01-02")).
WithKindFormatter(reflect.Int, PrintfCellFormatter("%d")).
WithDefaultFormatter(SprintCellFormatter(false))
str, raw, err := formatter.FormatCell(ctx, view, 0, 0)
// Automatically routes to the correct formatter based on cell type
func NewReflectTypeCellFormatter ¶
func NewReflectTypeCellFormatter() *ReflectTypeCellFormatter
NewReflectTypeCellFormatter creates a new empty ReflectTypeCellFormatter. Use the With* methods to configure type mappings.
Example:
formatter := NewReflectTypeCellFormatter().
WithTypeFormatter(reflect.TypeOf(time.Time{}), LayoutFormatter("2006-01-02")).
WithKindFormatter(reflect.Float64, PrintfCellFormatter("%.2f"))
func (*ReflectTypeCellFormatter) FormatCell ¶
func (f *ReflectTypeCellFormatter) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements CellFormatter by routing to the appropriate formatter based on the cell value's reflected type.
Matching algorithm:
- Check Types map for exact type match
- Check InterfaceTypes map for interface implementations
- Check Kinds map for reflect.Kind match
- If pointer type and not matched, dereference and try steps 1-3 on dereferenced type
- Use Default formatter if configured
- Return errors.ErrUnsupported if no match found
At each step, if a formatter returns errors.ErrUnsupported, the algorithm continues to the next step. Any other error is returned immediately.
Parameters:
- ctx: Context for cancellation and timeout
- view: The table view containing the cell
- row: Zero-based row index
- col: Zero-based column index
Returns:
- str: The formatted string from the matched formatter
- raw: Whether the output is raw (from the matched formatter)
- err: Error from formatter, or errors.ErrUnsupported if no match
func (*ReflectTypeCellFormatter) WithDefaultFormatter ¶
func (f *ReflectTypeCellFormatter) WithDefaultFormatter(fmt CellFormatter) *ReflectTypeCellFormatter
WithDefaultFormatter returns a new ReflectTypeCellFormatter with a default formatter set. This creates a copy, leaving the original unchanged.
The default formatter is used as the ultimate fallback when no type, interface, or kind formatters match. If no default is set and no formatters match, errors.ErrUnsupported is returned, allowing the formatter to be used in chains.
Parameters:
- fmt: The CellFormatter to use as default
Returns:
- A new ReflectTypeCellFormatter with the default formatter set
Example:
// Use fmt.Sprint as fallback for unmatched types
formatter := base.WithDefaultFormatter(SprintCellFormatter(false))
// Use a custom fallback that logs unmatched types
formatter = base.WithDefaultFormatter(
CellFormatterFunc(func(ctx context.Context, view View, row, col int) (string, bool, error) {
val := view.Cell(row, col)
log.Printf("unmatched type: %T", val)
return fmt.Sprint(val), false, nil
}),
)
func (*ReflectTypeCellFormatter) WithInterfaceTypeFormatter ¶
func (f *ReflectTypeCellFormatter) WithInterfaceTypeFormatter(typ reflect.Type, fmt CellFormatter) *ReflectTypeCellFormatter
WithInterfaceTypeFormatter returns a new ReflectTypeCellFormatter with an interface type formatter added. This creates a copy, leaving the original unchanged.
Interface type formatters match any cell value whose type implements the specified interface. This is checked after exact type matching but before kind matching.
Parameters:
- typ: The interface type to match (use reflect.TypeOf((*InterfaceName)(nil)).Elem())
- fmt: The CellFormatter to use for types implementing this interface
Returns:
- A new ReflectTypeCellFormatter with the interface formatter added
Example:
// Format any type implementing fmt.Stringer with its String() method
stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
formatter := base.WithInterfaceTypeFormatter(
stringerType,
CellFormatterFunc(func(ctx context.Context, view View, row, col int) (string, bool, error) {
return view.Cell(row, col).(fmt.Stringer).String(), false, nil
}),
)
// Format any type implementing a custom interface
formatterInterface := reflect.TypeOf((*CustomFormatter)(nil)).Elem()
formatter = formatter.WithInterfaceTypeFormatter(formatterInterface, myFormatter)
func (*ReflectTypeCellFormatter) WithKindFormatter ¶
func (f *ReflectTypeCellFormatter) WithKindFormatter(kind reflect.Kind, fmt CellFormatter) *ReflectTypeCellFormatter
WithKindFormatter returns a new ReflectTypeCellFormatter with a kind formatter added. This creates a copy, leaving the original unchanged.
Kind formatters provide broad category matching for types with the same reflect.Kind (int, string, struct, etc.). This is checked after exact type and interface matching, providing a coarse-grained fallback before the default formatter.
Parameters:
- kind: The reflect.Kind to match (e.g., reflect.Int, reflect.String)
- fmt: The CellFormatter to use for this kind
Returns:
- A new ReflectTypeCellFormatter with the kind formatter added
Example:
// Format all integer types with zero-padding
formatter := base.WithKindFormatter(
reflect.Int,
PrintfCellFormatter("%05d"),
)
// Format all float types with 2 decimal places
formatter = formatter.WithKindFormatter(
reflect.Float64,
PrintfCellFormatter("%.2f"),
)
// Format all struct types with JSON
formatter = formatter.WithKindFormatter(
reflect.Struct,
customJSONFormatter,
)
func (*ReflectTypeCellFormatter) WithTypeFormatter ¶
func (f *ReflectTypeCellFormatter) WithTypeFormatter(typ reflect.Type, fmt CellFormatter) *ReflectTypeCellFormatter
WithTypeFormatter returns a new ReflectTypeCellFormatter with an exact type formatter added. This creates a copy of the formatter, leaving the original unchanged (immutable builder pattern).
The type formatter has the highest priority in the matching hierarchy and will be tried before interface, kind, or default formatters.
Parameters:
- typ: The exact reflect.Type to match
- fmt: The CellFormatter to use for this type
Returns:
- A new ReflectTypeCellFormatter with the type formatter added
Example:
// Format time.Time values with custom layout
formatter := base.WithTypeFormatter(
reflect.TypeOf(time.Time{}),
LayoutFormatter("2006-01-02"),
)
// Format custom type with specific formatter
formatter = formatter.WithTypeFormatter(
reflect.TypeOf(MyCustomType{}),
PrintfCellFormatter("custom: %v"),
)
type ReflectValuesView ¶
type ReflectValuesView struct {
// Tit is the title of this view, returned by the Title() method.
Tit string
// Cols contains the column names defining both the column headers
// and the number of columns in this view.
Cols []string
// Rows contains the data rows, where each row is a slice of reflect.Value.
// Each cell is stored as a reflect.Value for introspection capabilities.
Rows [][]reflect.Value
}
ReflectValuesView is a View implementation that stores cells as reflect.Value.
This view type is designed for advanced use cases requiring reflection-based operations on cell data. Each cell is stored as a reflect.Value, providing access to type information and reflection capabilities while maintaining the View interface.
The underlying data structure uses [][]reflect.Value, where each cell can be introspected, compared, and manipulated using Go's reflection package. This is particularly useful for generic operations, type discovery, and dynamic value manipulation.
ReflectValuesView implements both View and ReflectCellView interfaces, providing both standard Cell() and specialized ReflectCell() methods.
Performance characteristics:
- Direct memory access: O(1) for cell retrieval
- Reflection overhead for value operations
- Higher memory usage than AnyValuesView due to reflect.Value storage
- Efficient for reflection-heavy operations
When to use ReflectValuesView:
- Need type introspection on cell values
- Implementing generic algorithms that work with arbitrary types
- Converting between different representation forms
- Caching reflection results for performance
- Building dynamic view transformations
- Working with code generation or analysis tools
Example usage:
source := retable.NewStringsView("Data", data, "A", "B")
reflected, _ := retable.NewReflectValuesViewFrom(source)
// Access via standard interface
value := reflected.Cell(0, 0)
// Access via reflection interface
reflectVal := reflected.ReflectCell(0, 0)
fmt.Printf("Type: %s, Kind: %s\n", reflectVal.Type(), reflectVal.Kind())
Thread safety: Not thread-safe. External synchronization required for concurrent access.
func NewReflectValuesViewFrom ¶
func NewReflectValuesViewFrom(source View) (*ReflectValuesView, error)
NewReflectValuesViewFrom creates a ReflectValuesView by reading and caching all cells from the source View as reflect.Value instances.
This function materializes all data from the source view into memory, converting each cell to a reflect.Value. If the source implements ReflectCellView, it uses ReflectCell() directly; otherwise, it wraps Cell() results with reflect.ValueOf().
This is useful for:
- Caching reflection results for repeated introspection
- Converting any view to a reflection-based representation
- Preparing data for reflection-heavy operations
- Type analysis and schema discovery
Parameters:
- source: The View to read data from
Returns:
- A new ReflectValuesView containing all data as reflect.Values
- An error if source is nil
Example:
original := retable.NewStringsView("Data", rows, "A", "B", "C")
reflected, err := retable.NewReflectValuesViewFrom(original)
if err != nil {
log.Fatal(err)
}
// All cells are now stored as reflect.Value for introspection
for row := 0; row < reflected.NumRows(); row++ {
for col := range reflected.Columns() {
val := reflected.ReflectCell(row, col)
fmt.Printf("Type: %s\n", val.Type())
}
}
Time complexity: O(rows * cols) for copying all cells Space complexity: O(rows * cols) for storing all reflect.Values
func (*ReflectValuesView) Cell ¶
func (view *ReflectValuesView) Cell(row, col int) any
Cell returns the interface value at the specified row and column indices.
This method extracts the underlying value from the reflect.Value using Interface(). If you need the reflect.Value itself for introspection, use ReflectCell() instead.
Parameters:
- row: Zero-based row index (0 to NumRows()-1)
- col: Zero-based column index (0 to len(Columns())-1)
Returns:
- The cell's underlying value (via reflect.Value.Interface()) if indices are valid
- nil if row or col indices are out of bounds
Time complexity: O(1)
func (*ReflectValuesView) Columns ¶
func (view *ReflectValuesView) Columns() []string
Columns returns the column names of this view.
func (*ReflectValuesView) NumRows ¶
func (view *ReflectValuesView) NumRows() int
NumRows returns the number of data rows in this view.
func (*ReflectValuesView) ReflectCell ¶
func (view *ReflectValuesView) ReflectCell(row, col int) reflect.Value
ReflectCell returns the reflect.Value at the specified row and column indices.
This method provides direct access to the stored reflect.Value, allowing for type introspection, comparison, and other reflection operations without extracting the underlying interface value.
Parameters:
- row: Zero-based row index (0 to NumRows()-1)
- col: Zero-based column index (0 to len(Columns())-1)
Returns:
- The reflect.Value stored at the cell if indices are valid
- An invalid reflect.Value (reflect.Value{}) if indices are out of bounds
To check if the returned value is valid, use reflectVal.IsValid().
Example:
val := view.ReflectCell(0, 0)
if val.IsValid() {
fmt.Printf("Type: %s, Kind: %s\n", val.Type(), val.Kind())
if val.Kind() == reflect.Int {
fmt.Printf("Int value: %d\n", val.Int())
}
}
Time complexity: O(1)
func (*ReflectValuesView) Title ¶
func (view *ReflectValuesView) Title() string
Title returns the title of this view.
type Scanner ¶
type Scanner interface {
// ScanString parses a string value into the destination reflect.Value.
//
// The dest parameter must be settable (obtained from a pointer's Elem()).
// The scanner should check dest's type and use the appropriate parser method
// to convert the string.
//
// Returns errors.ErrUnsupported if the scanner doesn't support the dest type,
// allowing scanner chains. Other errors indicate actual parsing failures.
//
// Parameters:
// - dest: The settable reflect.Value to write the parsed value into
// - str: The string to parse
// - parser: The Parser to use for primitive type conversions
//
// Returns:
// - error: Parsing error, or errors.ErrUnsupported if type not supported
ScanString(dest reflect.Value, str string, parser Parser) error
}
Scanner is the interface for parsing string values into Go values using reflection. This is the inverse operation of formatting - it converts string representations back into typed Go values.
Scanners work in conjunction with Parsers to handle the actual string-to-value conversion. The Scanner is responsible for orchestrating the conversion, while the Parser provides the primitive parsing operations (int, float, bool, time, etc.).
The Scanner interface operates at a lower level than the table system, working directly with reflect.Value. This makes it reusable across different contexts beyond just table cell parsing.
Design pattern: Scanners should check the dest type and call the appropriate Parser method to convert the string into the target type. For complex types, scanners may need to perform additional logic (e.g., splitting strings, handling null values, type conversions).
Example usage:
scanner := ScannerFunc(func(dest reflect.Value, str string, parser Parser) error {
if dest.Kind() == reflect.Int {
i, err := parser.ParseInt(str)
if err != nil {
return err
}
dest.SetInt(i)
return nil
}
return errors.ErrUnsupported
})
var result int
destValue := reflect.ValueOf(&result).Elem()
err := scanner.ScanString(destValue, "42", parser)
// result == 42
type ScannerFunc ¶
ScannerFunc is a function type that implements the Scanner interface, allowing plain functions to be used as Scanners.
This adapter type follows the common Go pattern of defining a function type that implements an interface (similar to http.HandlerFunc), making it easy to create inline scanners without defining separate types.
Example:
var scanner Scanner = ScannerFunc(func(dest reflect.Value, str string, parser Parser) error {
if dest.Type() == reflect.TypeOf(time.Time{}) {
t, err := parser.ParseTime(str)
if err != nil {
return err
}
dest.Set(reflect.ValueOf(t))
return nil
}
return errors.ErrUnsupported
})
func (ScannerFunc) ScanString ¶
ScanString implements the Scanner interface by calling the function itself.
type SingleReflectValueView ¶
type SingleReflectValueView struct {
// Tit is the title of this view, usually inherited from the source.
Tit string
// Col is the name of the single column in this view.
Col string
// Val is the reflect.Value stored in the single cell.
Val reflect.Value
}
SingleReflectValueView is a View implementation containing exactly one cell stored as a reflect.Value.
This specialized view type represents a 1x1 table (single row, single column) where the cell value is stored as a reflect.Value. It implements both View and ReflectCellView interfaces.
SingleReflectValueView is useful for:
- Extracting and wrapping a single cell from a larger view
- Representing scalar values as a minimal view
- Type introspection on individual values
- Building view hierarchies where leaf nodes are single values
Performance characteristics:
- Minimal memory footprint (one value + metadata)
- O(1) access time
- No allocations for repeated access
Example usage:
// Extract a single cell from another view
source := retable.NewStringsView("Data", rows, "A", "B", "C")
singleCell := retable.NewSingleReflectValueView(source, 0, 1)
fmt.Println(singleCell.Cell(0, 0)) // Value from source row 0, col 1
Thread safety: Immutable after creation, safe for concurrent reads.
func NewSingleReflectValueView ¶
func NewSingleReflectValueView(source View, row, col int) *SingleReflectValueView
NewSingleReflectValueView creates a SingleReflectValueView by extracting a single cell from the source View at the specified row and column.
The function reads one cell value from the source and wraps it in a minimal 1x1 view. The title is inherited from the source, and the column name is taken from the source's column at the specified index.
Parameters:
- source: The View to extract the cell from
- row: Zero-based row index in the source view
- col: Zero-based column index in the source view
Returns a new SingleReflectValueView containing the single cell value. If source is nil or indices are invalid, returns a view with only the title set (and an invalid reflect.Value).
Example:
source := retable.NewStringsView(
"Products",
[][]string{
{"Widget", "9.99"},
{"Gadget", "19.99"},
},
"Name", "Price",
)
priceView := retable.NewSingleReflectValueView(source, 1, 1)
fmt.Println(priceView.Cell(0, 0)) // Output: 19.99
func (*SingleReflectValueView) Cell ¶
func (view *SingleReflectValueView) Cell(row, col int) any
Cell returns the underlying interface value of the single cell.
Parameters:
- row: Must be 0 (only one row exists)
- col: Must be 0 (only one column exists)
Returns:
- The underlying value extracted via Val.Interface() if row and col are both 0
- nil if row is not 0 or col is not 0
Time complexity: O(1)
func (*SingleReflectValueView) Columns ¶
func (view *SingleReflectValueView) Columns() []string
Columns returns a slice containing the single column name.
func (*SingleReflectValueView) NumRows ¶
func (view *SingleReflectValueView) NumRows() int
NumRows always returns 1 for SingleReflectValueView.
func (*SingleReflectValueView) ReflectCell ¶
func (view *SingleReflectValueView) ReflectCell(row, col int) reflect.Value
ReflectCell returns the reflect.Value of the single cell.
Parameters:
- row: Must be 0 (only one row exists)
- col: Must be 0 (only one column exists)
Returns:
- The reflect.Value stored in Val if row and col are both 0
- An invalid reflect.Value (reflect.Value{}) if row is not 0 or col is not 0
Time complexity: O(1)
func (*SingleReflectValueView) Title ¶
func (view *SingleReflectValueView) Title() string
Title returns the title of this view.
type SprintFormatter ¶
type SprintFormatter struct{}
SprintFormatter is a universal Formatter that uses fmt.Sprint to format any value. This formatter never returns an error and accepts all value types.
It extracts the actual Go value using reflect.Value.Interface() and formats it using fmt.Sprint, which uses the value's String() method if available, or falls back to the default Go formatting.
Example:
var f Formatter = SprintFormatter{}
str, _ := f.Format(reflect.ValueOf(time.Now()))
// str contains the time formatted by time.Time's String() method
type StringIfTrue ¶
type StringIfTrue string
StringIfTrue formats boolean cells by returning a specified string for true values and an empty string for false values. The output is marked as non-raw.
This formatter is useful for creating checkmarks, labels, or indicators that only appear when a condition is true. It panics if the cell value is not a bool.
Example usage:
// Show checkmark for true, nothing for false
formatter := StringIfTrue("✓")
str, raw, _ := formatter.FormatCell(ctx, view, 0, 0)
// If cell value is true: str == "✓", raw == false
// If cell value is false: str == "", raw == false
// Show "Active" for true, nothing for false
statusFormatter := StringIfTrue("Active")
func (StringIfTrue) FormatCell ¶
func (f StringIfTrue) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements CellFormatter for boolean values. Returns the string value for true, empty string for false. Panics if the cell value is not a bool. Never returns an error.
type StringParser ¶
type StringParser struct {
// TrueStrings lists all strings that should be parsed as boolean true.
// Default includes: "true", "True", "TRUE", "yes", "Yes", "YES", "1"
TrueStrings []string `json:"trueStrings"`
// FalseStrings lists all strings that should be parsed as boolean false.
// Default includes: "false", "False", "FALSE", "no", "No", "NO", "0"
FalseStrings []string `json:"falseStrings"`
// NilStrings lists all strings that represent nil/null values.
// Default includes: "", "nil", "<nil>", "null", "NULL"
// This is used by higher-level scanning logic to detect null values.
NilStrings []string `json:"nilStrings"`
// TimeFormats lists time layout strings to try when parsing time values.
// Formats are tried in order until one succeeds.
// Default includes RFC3339, ISO dates, and several common formats.
TimeFormats []string `json:"timeFormats"`
}
StringParser is a configurable implementation of the Parser interface that handles string-to-value conversions with support for multiple conventions and formats.
Key features:
- Configurable boolean string representations (e.g., "yes"/"no", "1"/"0")
- Configurable nil/null string representations
- Multiple time format attempts for flexible time parsing
- Locale-aware float parsing (e.g., handling comma decimal separators)
The StringParser uses standard Go parsing functions (strconv, time.Parse) internally, but adds flexibility through configuration and fallback strategies.
Example usage:
parser := NewStringParser()
// Parser comes pre-configured with sensible defaults
// Customize boolean parsing
parser.TrueStrings = append(parser.TrueStrings, "enabled", "on")
parser.FalseStrings = append(parser.FalseStrings, "disabled", "off")
// Add custom time formats
parser.TimeFormats = append([]string{"01/02/2006"}, parser.TimeFormats...)
// Parse with custom configuration
b, _ := parser.ParseBool("enabled") // true
t, _ := parser.ParseTime("03/15/2024") // uses custom format
func NewStringParser ¶
func NewStringParser() *StringParser
NewStringParser creates a new StringParser with sensible default configurations.
Default configurations:
- TrueStrings: "true", "True", "TRUE", "yes", "Yes", "YES", "1"
- FalseStrings: "false", "False", "FALSE", "no", "No", "NO", "0"
- NilStrings: "", "nil", "<nil>", "null", "NULL"
- TimeFormats: Comprehensive list of common formats (RFC3339, ISO, etc.)
The returned parser can be used as-is or customized by modifying its fields.
Returns:
- A new StringParser with default configurations
Example:
parser := NewStringParser()
// Use with defaults
value, _ := parser.ParseBool("yes") // true
// Or customize
parser.TrueStrings = append(parser.TrueStrings, "ja", "oui", "si")
func (*StringParser) ParseBool ¶
func (p *StringParser) ParseBool(str string) (bool, error)
ParseBool parses a string into a boolean value based on the configured TrueStrings and FalseStrings lists.
The parser checks if the string exactly matches (case-sensitive) any string in TrueStrings or FalseStrings. If no match is found, an error is returned.
Parameters:
- str: The string to parse
Returns:
- bool: The parsed boolean value
- error: Error if string doesn't match any configured true/false string
Example:
parser := NewStringParser()
b, _ := parser.ParseBool("true") // true, nil
b, _ := parser.ParseBool("yes") // true, nil
b, _ := parser.ParseBool("1") // true, nil
b, _ := parser.ParseBool("false") // false, nil
b, _ := parser.ParseBool("no") // false, nil
b, err := parser.ParseBool("maybe") // false, error
func (*StringParser) ParseDuration ¶
func (p *StringParser) ParseDuration(str string) (time.Duration, error)
ParseDuration parses a string into a time.Duration value. Uses Go's standard time.ParseDuration which accepts strings like "1h30m", "5s", "100ms".
Valid units: "ns" (nanoseconds), "us" (microseconds), "ms" (milliseconds), "s" (seconds), "m" (minutes), "h" (hours).
Parameters:
- str: The string to parse (e.g., "1h30m", "5s")
Returns:
- time.Duration: The parsed duration value
- error: Error if string is not a valid duration format
Example:
d, _ := parser.ParseDuration("1h30m") // 1 hour 30 minutes
d, _ := parser.ParseDuration("5s") // 5 seconds
d, _ := parser.ParseDuration("100ms") // 100 milliseconds
d, _ := parser.ParseDuration("2h45m30s") // 2 hours 45 minutes 30 seconds
_, err := parser.ParseDuration("invalid") // error
func (*StringParser) ParseFloat ¶
func (p *StringParser) ParseFloat(str string) (float64, error)
ParseFloat parses a string into a 64-bit floating point number with locale awareness. This method tries multiple strategies to handle different number formats:
- Standard parsing using strconv.ParseFloat (handles "123.45")
- If that fails, tries handling comma as decimal separator ("123,45" -> "123.45")
- More strategies could be added for thousand separators, etc.
This flexibility is important for parsing numbers from different locales or user input.
Parameters:
- str: The string to parse
Returns:
- float64: The parsed floating point value
- error: Parsing error if the string is not a valid number in any recognized format
Example:
f, _ := parser.ParseFloat("3.14") // 3.14 (standard)
f, _ := parser.ParseFloat("3,14") // 3.14 (comma decimal)
f, _ := parser.ParseFloat("-2.5e10") // -2.5e10 (scientific notation)
func (*StringParser) ParseInt ¶
func (p *StringParser) ParseInt(str string) (int64, error)
ParseInt parses a string into a 64-bit signed integer using base 10. Uses strconv.ParseInt internally.
Parameters:
- str: The string to parse
Returns:
- int64: The parsed integer value
- error: Parsing error if the string is not a valid integer
Example:
i, err := parser.ParseInt("42") // 42, nil
i, err := parser.ParseInt("-123") // -123, nil
i, err := parser.ParseInt("abc") // 0, error
func (*StringParser) ParseTime ¶
func (p *StringParser) ParseTime(str string) (time.Time, error)
ParseTime parses a string into a time.Time value by trying multiple time formats.
The parser tries each format in the TimeFormats slice in order until one succeeds. This allows parsing of various time string formats without needing to know the exact format in advance.
The default TimeFormats include RFC3339, ISO 8601, common date formats, and several others. You can customize the TimeFormats slice to add or prioritize specific formats.
Parameters:
- str: The string to parse
Returns:
- time.Time: The parsed time value
- error: Error if string doesn't match any configured time format
Example:
parser := NewStringParser()
t, _ := parser.ParseTime("2024-03-15T14:30:00Z") // RFC3339
t, _ := parser.ParseTime("2024-03-15") // ISO date
t, _ := parser.ParseTime("15.03.2024") // German date format
t, _ := parser.ParseTime("2024-03-15 14:30:00") // DateTime format
_, err := parser.ParseTime("not a date") // error
func (*StringParser) ParseUint ¶
func (p *StringParser) ParseUint(str string) (uint64, error)
ParseUint parses a string into a 64-bit unsigned integer using base 10. Uses strconv.ParseUint internally.
Parameters:
- str: The string to parse
Returns:
- uint64: The parsed unsigned integer value
- error: Parsing error if the string is not a valid unsigned integer
Example:
u, err := parser.ParseUint("42") // 42, nil
u, err := parser.ParseUint("0") // 0, nil
u, err := parser.ParseUint("-1") // 0, error (negative not allowed)
type StringsView ¶
type StringsView struct {
// Tit is the title of this view, returned by the Title() method.
Tit string
// Cols contains the column names defining both the column headers
// and the number of columns in this view.
Cols []string
// Rows contains the data rows, where each row is a slice of strings.
// Rows can have fewer elements than len(Cols) for sparse data support.
Rows [][]string
}
StringsView is a View implementation that uses strings as cell values. It is the most straightforward and commonly used view type for tabular string data.
The Cols field defines the column names and determines the number of columns. Each element in Rows represents a row of data, where each row is a slice of strings.
StringsView supports sparse data: a row within Rows can have fewer slice elements than Cols, in which case empty strings ("") are returned as values for missing cells. This allows for efficient storage of tables with many empty cells.
Performance characteristics:
- Direct memory access: O(1) for cell retrieval
- Memory efficient for string-only data
- No type conversion overhead
When to use StringsView:
- Working with CSV, TSV, or other text-based tabular data
- All cell values are strings or can be represented as strings
- Need simple, fast access to string data
- Memory efficiency is important for large string tables
Example usage:
view := retable.NewStringsView(
"Products",
[][]string{
{"ID", "Name", "Price"},
{"1", "Widget", "9.99"},
{"2", "Gadget", "19.99"},
},
)
fmt.Println(view.Cell(0, 1)) // Output: Widget
Sparse data example:
view := &retable.StringsView{
Cols: []string{"A", "B", "C"},
Rows: [][]string{
{"1", "2", "3"},
{"4"}, // Missing B and C - will return empty strings
},
}
fmt.Println(view.Cell(1, 2)) // Output: "" (empty string)
func NewStringsView ¶
func NewStringsView(title string, rows [][]string, cols ...string) *StringsView
NewStringsView creates a new StringsView with flexible column specification.
The function accepts column names in two ways:
- Explicit columns: Pass column names via the cols variadic parameter
- Header row: If no cols are provided and rows is not empty, the first row is used as column names and removed from the data rows
All column names have leading and trailing whitespace trimmed automatically.
Parameters:
- title: The title for this view (can be empty string)
- rows: The data rows, or rows including a header row if cols is empty
- cols: Optional column names. If empty, first row is used as header
Returns a new StringsView with the specified title, columns, and data rows.
Example with explicit columns:
view := retable.NewStringsView(
"Users",
[][]string{
{"alice", "30"},
{"bob", "25"},
},
"Name", "Age",
)
Example with header row:
view := retable.NewStringsView(
"Users",
[][]string{
{"Name", "Age"}, // This becomes the header
{"alice", "30"},
{"bob", "25"},
},
)
func (*StringsView) Cell ¶
func (view *StringsView) Cell(row, col int) any
Cell returns the value at the specified row and column indices.
For StringsView, Cell returns:
- The string value at [row][col] if the cell exists
- An empty string "" if the row exists but has fewer columns (sparse data)
- nil if row or col indices are out of bounds
Parameters:
- row: Zero-based row index (0 to NumRows()-1)
- col: Zero-based column index (0 to len(Columns())-1)
Returns the cell value as any, which will be either string, "", or nil.
Time complexity: O(1)
func (*StringsView) Columns ¶
func (view *StringsView) Columns() []string
Columns returns the column names of this view.
func (*StringsView) NumRows ¶
func (view *StringsView) NumRows() int
NumRows returns the number of data rows in this view.
func (*StringsView) Title ¶
func (view *StringsView) Title() string
Title returns the title of this view.
type StringsViewer ¶
type StringsViewer struct {
// Cols specifies the column names to use when creating Views.
// Can be empty or nil, in which case column names will be handled
// by the underlying View implementation (NewStringsView).
Cols []string
}
StringsViewer is a Viewer implementation that creates Views from two-dimensional string slices ([][]string).
This is one of the simplest Viewer implementations and is useful when working with raw string data, such as CSV files, database query results converted to strings, or any tabular data represented as strings.
Column Names:
The Cols field can be used to specify column names for the Views. If provided, these names will be used as the column headers in the resulting View. If Cols is empty or nil, NewStringsView will use default column names or the table may be treated as having no headers.
The behavior when Cols is provided:
- If Cols has fewer entries than columns in the data, remaining columns will use default names
- If Cols has more entries than columns in the data, extra names are ignored
Example:
// Create a StringsViewer with column names
viewer := StringsViewer{
Cols: []string{"Name", "Age", "City"},
}
// Use it to create a View from string data
data := [][]string{
{"Alice", "30", "NYC"},
{"Bob", "25", "LA"},
}
view, err := viewer.NewView("People", data)
// Create a StringsViewer without predefined columns
viewer := StringsViewer{}
view, err := viewer.NewView("Data", data)
func (StringsViewer) NewView ¶
func (v StringsViewer) NewView(title string, table any) (View, error)
NewView creates a View from a [][]string table.
This method implements the Viewer interface and is the primary way to create Views from string data using StringsViewer.
The table parameter must be of type [][]string where:
- Each inner slice represents one row of data
- All rows should have the same number of elements (columns) for best results
- Empty tables (nil or zero length) are accepted and will create empty Views
Column names are taken from the Cols field of the StringsViewer. See the StringsViewer type documentation for details on column naming.
Parameters:
- title: The title for the resulting View. Can be empty. This title is returned by the View's Title() method.
- table: The data to create the View from. Must be of type [][]string.
Returns:
- View: The created View containing the string data with the specified title.
- error: nil on success, or an error if table is not of type [][]string.
Example:
viewer := StringsViewer{Cols: []string{"Product", "Price", "Stock"}}
inventory := [][]string{
{"Widget", "9.99", "100"},
{"Gadget", "19.99", "50"},
{"Doohickey", "29.99", "25"},
}
view, err := viewer.NewView("Inventory", inventory)
if err != nil {
log.Fatal(err)
}
fmt.Println(view.Title()) // "Inventory"
fmt.Println(view.Columns()) // ["Product", "Price", "Stock"]
fmt.Println(view.NumRows()) // 3
// Access data through the View interface
cell := view.Cell(0, 0) // "Widget"
type StructFieldNaming ¶
type StructFieldNaming struct {
// Tag is the struct field tag to be used as column title.
// When non-empty, struct fields are checked for this tag name,
// and the tag value is used as the column title.
//
// Tag values support comma-separated options, where only the first
// part before the comma is used as the column title:
// `json:"name,omitempty"` results in column title "name"
//
// If Tag is empty, then every struct field will be treated as untagged.
Tag string
// Ignore specifies a column title value that marks fields to be excluded.
// Any field whose column title matches this value will be ignored and
// not appear in the resulting table view.
//
// Common patterns:
// - Use "-" to match the convention `json:"-"` for ignored fields
// - Use "" (empty string) to ignore all unexported or empty-titled fields
Ignore string
// Untagged is called with the struct field name to generate a column title
// when the field has no tag matching Tag (or Tag is empty).
//
// If Untagged is nil, the raw struct field name is used as the column title.
//
// Common functions to use:
// - SpacePascalCase: "FirstName" -> "First Name"
// - SpaceGoCase: "HTTPServer" -> "HTTP Server"
// - Custom function for domain-specific naming conventions
Untagged func(fieldName string) (column string)
}
StructFieldNaming defines how struct fields are mapped to column titles as used by View. It provides flexible control over which struct fields become columns and what their column titles are through struct tags and custom naming functions.
The mapping process follows these rules:
- Only exported (public) struct fields are considered for column mapping
- Anonymous embedded struct fields are recursively processed, with their fields inlined
- If a Tag is specified, the struct tag value is used as the column title
- For fields without tags (or if Tag is empty), the Untagged function determines the column title
- Fields matching the Ignore value are excluded from the table
Example usage with struct tags:
type Person struct {
FirstName string `csv:"first_name"`
LastName string `csv:"last_name"`
Age int `csv:"age"`
Internal string `csv:"-"` // ignored when Ignore is set to "-"
}
naming := &StructFieldNaming{
Tag: "csv",
Ignore: "-",
}
columns := naming.Columns(&Person{}) // ["first_name", "last_name", "age"]
Example with custom naming function:
naming := &StructFieldNaming{
Untagged: SpacePascalCase, // "FirstName" becomes "First Name"
}
A nil *StructFieldNaming is a valid value and is equivalent to the zero value, which will use all exported struct fields with their field name as column title.
StructFieldNaming implements the Viewer interface, allowing it to create Views from slices or arrays of structs.
func (*StructFieldNaming) ColumnStructFieldValue ¶
func (n *StructFieldNaming) ColumnStructFieldValue(structVal reflect.Value, column string) reflect.Value
ColumnStructFieldValue returns the reflect.Value of the struct field that is mapped to the given column title.
The function searches through all exported struct fields (including those from anonymously embedded structs) to find the field whose column title matches the given column parameter.
Anonymous embedded structs are recursively searched, allowing fields from nested structs to be accessed by their column title.
Returns an invalid reflect.Value if:
- The column is ignored (empty or matches Ignore)
- No struct field maps to the given column title
- structVal is not a struct or pointer to struct
Panics if structVal is not a struct or pointer to struct type.
This method is safe to call with a nil receiver.
Example:
type Person struct {
Name string `csv:"full_name"`
Age int `csv:"age"`
}
naming := &StructFieldNaming{Tag: "csv"}
person := Person{Name: "John", Age: 30}
val := naming.ColumnStructFieldValue(reflect.ValueOf(person), "full_name")
// val.String() == "John"
func (*StructFieldNaming) Columns ¶
func (n *StructFieldNaming) Columns(strct any) []string
Columns returns the column titles for all non-ignored exported fields of a struct or pointer to a struct.
The returned slice contains column titles in the order they appear in the struct definition, with fields from anonymously embedded structs inlined in their position.
Fields are processed according to the StructFieldNaming rules:
- Tag determines which struct tag to read for column titles
- Untagged function transforms field names when no tag is present
- Ignore value filters out unwanted columns
Panics if strct is not a struct or pointer to struct type.
This method is safe to call with a nil receiver, in which case it returns all exported field names without transformation.
Example:
type User struct {
ID int `db:"user_id"`
Username string `db:"username"`
Password string `db:"-"`
}
naming := &StructFieldNaming{Tag: "db", Ignore: "-"}
cols := naming.Columns(&User{}) // ["user_id", "username"]
func (*StructFieldNaming) IsIgnored ¶
func (n *StructFieldNaming) IsIgnored(column string) bool
IsIgnored returns true if the given column title should be ignored and excluded from the table view.
A column is considered ignored if:
- The column title is an empty string
- The column title matches the Ignore field value (when n is not nil)
This method is safe to call with a nil receiver.
func (*StructFieldNaming) NewView ¶
func (n *StructFieldNaming) NewView(title string, table any) (View, error)
NewView returns a View for a table made up of a slice or array of structs.
This method implements the Viewer interface for StructFieldNaming, creating a StructRowsView by delegating to StructRowsViewer with this naming configuration.
Parameters:
- title: The title for the table view
- table: A slice or array of structs (or pointers to structs)
Returns an error if table is not a slice or array, or if the element type is not a struct or pointer to struct.
Example:
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
}
naming := &StructFieldNaming{Tag: "json"}
products := []Product{{"Widget", 9.99}, {"Gadget", 19.99}}
view, err := naming.NewView("Products", products)
func (*StructFieldNaming) String ¶
func (n *StructFieldNaming) String() string
String implements the fmt.Stringer interface for StructFieldNaming.
Valid to call with nil receiver.
func (*StructFieldNaming) StructFieldColumn ¶
func (n *StructFieldNaming) StructFieldColumn(field reflect.StructField) string
StructFieldColumn returns the column title for a struct field using the naming rules defined in the StructFieldNaming configuration.
The function applies the following logic:
- Returns empty string for unexported or anonymous fields
- If Tag is set and the field has that tag, uses the tag value (before any comma)
- Otherwise calls Untagged function if set, or uses the field name
This method is safe to call with a nil receiver, in which case it returns the field name for exported fields and empty string for unexported fields.
func (*StructFieldNaming) WithIgnore ¶
func (n *StructFieldNaming) WithIgnore(ignore string) *StructFieldNaming
WithIgnore returns a copy of the StructFieldNaming with the Ignore field set to the specified value.
This method allows fluent configuration of the naming rules.
Example:
naming := (&StructFieldNaming{Tag: "csv"}).WithIgnore("-")
func (*StructFieldNaming) WithTag ¶
func (n *StructFieldNaming) WithTag(tag string) *StructFieldNaming
WithTag returns a copy of the StructFieldNaming with the Tag field set to the specified value.
This method allows fluent configuration of the naming rules.
Example:
naming := (&StructFieldNaming{}).WithTag("json").WithIgnore("-")
type StructRowsView ¶
type StructRowsView struct {
// contains filtered or unexported fields
}
StructRowsView is a View implementation that provides efficient access to tabular data stored in a slice or array of structs.
StructRowsView is created by StructRowsViewer.NewView and implements the View interface, allowing struct data to be formatted, exported, or displayed as tables.
The view uses reflection to extract field values on demand, with caching to optimize repeated access to the same row. This makes it efficient for scenarios where rows are accessed sequentially or where the same row is queried multiple times.
Field-to-column mapping:
- If indices is nil, struct fields map 1:1 to columns in declaration order
- If indices is non-nil, it specifies which struct field index maps to each column
- The indices slice has one entry per struct field, with values indicating column positions
- An index value of -1 means that field is excluded from the view
Performance characteristics:
- Row access is O(1) - direct slice indexing
- Cell access includes reflection overhead on first access per row
- Subsequent cell access for the same row uses cached values (O(1))
- Memory usage scales with the number of structs in the underlying slice
This type is thread-safe for concurrent read access.
func (*StructRowsView) Cell ¶
func (view *StructRowsView) Cell(row, col int) any
Cell returns the value at the specified row and column as an any (interface{}).
This method implements the View interface and uses reflection to extract the struct field value corresponding to the requested cell.
Caching behavior:
- On first access to a row, all field values are extracted and cached
- Subsequent Cell calls for the same row use the cached values
- Accessing a different row invalidates the cache and extracts that row's values
Parameters:
- row: Row index (0-based)
- col: Column index (0-based)
Returns nil if row or col is out of bounds.
Performance: O(1) for cached rows, O(n) where n is the number of struct fields for the first access to a new row.
func (*StructRowsView) Columns ¶
func (view *StructRowsView) Columns() []string
Columns returns the column names of the view in display order. This implements the View interface.
func (*StructRowsView) NumRows ¶
func (view *StructRowsView) NumRows() int
NumRows returns the number of rows in the view, which equals the length of the underlying struct slice or array. This implements the View interface.
func (*StructRowsView) ReflectCell ¶
func (view *StructRowsView) ReflectCell(row, col int) reflect.Value
ReflectCell returns the reflect.Value at the specified row and column.
This method is similar to Cell but returns a reflect.Value instead of any, allowing callers to perform type-specific operations or avoid unnecessary Interface() conversions.
Caching behavior:
- On first access to a row, all field reflect.Values are extracted and cached
- Subsequent ReflectCell calls for the same row use the cached values
- Accessing a different row invalidates the cache and extracts that row's values
Parameters:
- row: Row index (0-based)
- col: Column index (0-based)
Returns an invalid reflect.Value (reflect.Value{}) if row or col is out of bounds. Use reflect.Value.IsValid() to check if the returned value is valid.
Performance: O(1) for cached rows, O(n) where n is the number of struct fields for the first access to a new row.
Example:
val := view.ReflectCell(0, 0)
if val.IsValid() && val.CanInt() {
fmt.Println("Integer value:", val.Int())
}
func (*StructRowsView) Title ¶
func (view *StructRowsView) Title() string
Title returns the title of the view. This implements the View interface.
type StructRowsViewer ¶
type StructRowsViewer struct {
StructFieldNaming
// MapIndices maps struct field indices to column indices in the output view.
//
// The key is the index of a field as returned by StructFieldTypes (exported
// fields in declaration order, with embedded struct fields inlined).
// The value is the column index where that field's data should appear.
//
// If MapIndices is nil, fields map 1:1 to columns in their natural order.
//
// Mapping a field index to -1 excludes that field from the view entirely.
//
// All column indices from 0 to N-1 must be mapped exactly once (excluding -1),
// where N is the number of columns in the output.
//
// Example:
// MapIndices: map[int]int{
// 0: 1, // field 0 -> column 1
// 1: 0, // field 1 -> column 0 (swap with field 0)
// 2: -1, // field 2 -> excluded
// 3: 2, // field 3 -> column 2
// }
MapIndices map[int]int
}
StructRowsViewer implements Viewer for tables represented by a slice or array of struct rows, with advanced control over column ordering and field inclusion.
StructRowsViewer extends StructFieldNaming with the ability to rearrange columns and selectively exclude specific struct fields by their index. This is useful when:
- You need to reorder columns without changing the struct definition
- You want to exclude specific fields beyond what struct tags provide
- You need programmatic control over which fields appear in the view
The viewer uses reflection to extract field values from each struct in the slice, mapping them to columns according to the StructFieldNaming rules and MapIndices configuration.
Column mapping process:
- All exported struct fields are identified (including embedded struct fields)
- StructFieldNaming rules determine the column title for each field
- MapIndices (if set) remaps field indices to column positions
- Fields mapped to -1 are excluded from the output
- A StructRowsView is created to provide access to the data
Example with field reordering:
type Employee struct {
ID int
FirstName string
LastName string
Salary float64
}
viewer := &StructRowsViewer{
StructFieldNaming: StructFieldNaming{
Untagged: SpacePascalCase,
},
MapIndices: map[int]int{
0: 2, // ID appears in 3rd column
1: 0, // FirstName appears in 1st column
2: 1, // LastName appears in 2nd column
3: -1, // Salary is excluded
},
}
// Columns will be: ["First Name", "Last Name", "ID"]
Example with field exclusion:
viewer := &StructRowsViewer{}
viewer = viewer.WithIgnoreFieldIndex(3) // Ignore field at index 3
StructRowsViewer implements the Viewer interface.
func DefaultStructRowsViewer ¶
func DefaultStructRowsViewer() *StructRowsViewer
DefaultStructRowsViewer returns a new StructRowsViewer configured with DefaultStructFieldNaming.
The returned viewer:
- Uses "col" struct tag for column titles
- Ignores fields tagged with "-"
- Uses SpacePascalCase for untagged field names
- Has no column index mapping (MapIndices is nil)
This is a convenience function for creating a standard viewer for struct slices.
Example:
type Person struct {
Name string `col:"Full Name"`
Age int
}
viewer := DefaultStructRowsViewer()
view, err := viewer.NewView("People", []Person{{Name: "John", Age: 30}})
func NoTagsStructRowsViewer ¶
func NoTagsStructRowsViewer() *StructRowsViewer
NoTagsStructRowsViewer returns a new StructRowsViewer that ignores struct tags and uses raw field names as column titles.
The returned viewer:
- Does not read any struct tags
- Uses the exact struct field names as column titles (e.g., "FirstName", "LastName")
- Has no column index mapping (MapIndices is nil)
This is useful when you want to quickly display struct data without setting up tags, or when the raw field names are already suitable as column headers.
Example:
type Person struct {
Name string
Age int
}
viewer := NoTagsStructRowsViewer()
view, err := viewer.NewView("People", []Person{{Name: "John", Age: 30}})
// Column titles will be: "Name", "Age"
func (*StructRowsViewer) NewView ¶
func (v *StructRowsViewer) NewView(title string, table any) (View, error)
NewView returns a View for a table made up of a slice or array of structs.
This method implements the Viewer interface for StructRowsViewer. It uses reflection to analyze the struct type, apply the naming configuration, and create a StructRowsView that provides efficient access to the data.
The method performs the following steps:
- Validates that table is a slice or array
- Extracts the element type and verifies it's a struct
- Gets all exported struct fields using StructFieldTypes
- Applies StructFieldNaming rules to determine column titles
- Applies MapIndices remapping if configured
- Creates and returns a StructRowsView with the computed mapping
Parameters:
- title: The title for the table view
- table: Must be a slice or array of structs (or pointers to structs)
Returns an error if:
- table is not a slice or array type
- the element type is not a struct or pointer to struct
Example:
type Order struct {
OrderID int `csv:"id"`
Customer string `csv:"customer"`
Total float64 `csv:"total"`
}
viewer := &StructRowsViewer{
StructFieldNaming: StructFieldNaming{Tag: "csv"},
}
orders := []Order{{1, "Alice", 99.99}, {2, "Bob", 149.99}}
view, err := viewer.NewView("Orders", orders)
func (*StructRowsViewer) String ¶
func (v *StructRowsViewer) String() string
String implements the fmt.Stringer interface for StructRowsViewer.
func (*StructRowsViewer) WithIgnore ¶
func (v *StructRowsViewer) WithIgnore(ignore string) *StructRowsViewer
WithIgnore returns a copy of the StructRowsViewer with the Ignore field set to the specified value.
This method allows fluent configuration of the viewer.
Example:
viewer := (&StructRowsViewer{Tag: "csv"}).WithIgnore("-")
func (*StructRowsViewer) WithIgnoreField ¶
func (v *StructRowsViewer) WithIgnoreField(structPtr, fieldPtr any) *StructRowsViewer
WithIgnoreField returns a copy of the StructRowsViewer with the specified struct field excluded from the view.
This method uses reflection to determine the field index by comparing pointer addresses. It requires pointers to both a struct instance and one of its fields.
Panics if:
- structPtr is not a pointer to a struct
- fieldPtr is not a pointer to a field of that struct
- the field cannot be found in the struct
Example:
type Person struct {
Name string
Age int
Secret string
}
var p Person
viewer := (&StructRowsViewer{}).WithIgnoreField(&p, &p.Secret)
func (*StructRowsViewer) WithIgnoreFieldIndex ¶
func (v *StructRowsViewer) WithIgnoreFieldIndex(fieldIndex int) *StructRowsViewer
WithIgnoreFieldIndex returns a copy of the StructRowsViewer with the specified field index mapped to -1, effectively excluding it from the view.
This is a convenience method equivalent to WithMapIndex(fieldIndex, -1).
The fieldIndex is the index of the struct field as returned by StructFieldTypes.
Example:
// Exclude the 3rd field (index 2)
viewer := (&StructRowsViewer{}).WithIgnoreFieldIndex(2)
func (*StructRowsViewer) WithIgnoreFieldIndices ¶
func (v *StructRowsViewer) WithIgnoreFieldIndices(fieldIndices ...int) *StructRowsViewer
WithIgnoreFieldIndices returns a copy of the StructRowsViewer with multiple field indices mapped to -1, effectively excluding them from the view.
This is a convenience method for excluding multiple fields at once.
Example:
// Exclude fields at indices 2, 5, and 7
viewer := (&StructRowsViewer{}).WithIgnoreFieldIndices(2, 5, 7)
func (*StructRowsViewer) WithMapIndex ¶
func (v *StructRowsViewer) WithMapIndex(fieldIndex, columnIndex int) *StructRowsViewer
WithMapIndex returns a copy of the StructRowsViewer with an additional field-to-column mapping added to MapIndices.
The fieldIndex is the index of the struct field (as returned by StructFieldTypes), and columnIndex is the desired column position in the output (or -1 to exclude).
Example:
viewer := (&StructRowsViewer{}).
WithMapIndex(0, 2). // field 0 -> column 2
WithMapIndex(1, 0). // field 1 -> column 0
WithMapIndex(2, 1) // field 2 -> column 1
func (*StructRowsViewer) WithMapIndices ¶
func (v *StructRowsViewer) WithMapIndices(mapIndices map[int]int) *StructRowsViewer
WithMapIndices returns a copy of the StructRowsViewer with the MapIndices field replaced by the provided map.
This replaces any existing mappings with the new map. Use WithMapIndex to add individual mappings incrementally.
Example:
viewer := (&StructRowsViewer{}).WithMapIndices(map[int]int{
0: 2, // field 0 -> column 2
1: 0, // field 1 -> column 0
2: 1, // field 2 -> column 1
3: -1, // field 3 -> excluded
})
func (*StructRowsViewer) WithTag ¶
func (v *StructRowsViewer) WithTag(tag string) *StructRowsViewer
WithTag returns a copy of the StructRowsViewer with the Tag field set to the specified value.
This method allows fluent configuration of the viewer.
Example:
viewer := (&StructRowsViewer{}).WithTag("json").WithIgnore("-")
type UnsupportedCellFormatter ¶
type UnsupportedCellFormatter struct{}
UnsupportedCellFormatter is a CellFormatter that always returns errors.ErrUnsupported. This is useful as a placeholder or for explicitly marking certain cell types as unsupported in a formatting configuration.
In a formatter chain, this can be used to force trying the next formatter, or to document that a particular cell type intentionally has no formatter.
Example usage:
// Explicitly mark a type as unsupported
var formatter CellFormatter = UnsupportedCellFormatter{}
_, _, err := formatter.FormatCell(ctx, view, 0, 0)
// err == errors.ErrUnsupported
func (UnsupportedCellFormatter) FormatCell ¶
func (UnsupportedCellFormatter) FormatCell(ctx context.Context, view View, row, col int) (str string, raw bool, err error)
FormatCell implements CellFormatter by always returning errors.ErrUnsupported.
type UnsupportedFormatter ¶
type UnsupportedFormatter struct{}
UnsupportedFormatter is a Formatter that always returns errors.ErrUnsupported. This is useful as a placeholder or when you want to explicitly mark certain types as unsupported in a formatting chain.
Example:
// Mark a type as unsupported, forcing fallback to another formatter
var f Formatter = UnsupportedFormatter{}
_, err := f.Format(reflect.ValueOf("anything"))
// err == errors.ErrUnsupported
type View ¶
type View interface {
// Title returns the name/title of this table.
// The title is used for display purposes and when writing to formats
// that support named tables (e.g., Excel sheet names, HTML table captions).
// Returns an empty string if the table has no title.
Title() string
// Columns returns the names of all columns in this table.
// The length of the returned slice defines the number of columns.
// Individual column names may be empty strings if unnamed.
// The returned slice should not be modified by callers.
// Column names are used as headers when writing to CSV, HTML, etc.
Columns() []string
// NumRows returns the total number of data rows in this table.
// This does not include any header row - it's purely the count of data rows.
// Returns 0 for an empty table.
NumRows() int
// Cell returns the value at the specified row and column coordinates.
// Row and column indices are zero-based.
//
// Returns nil in these cases:
// - row is negative or >= NumRows()
// - col is negative or >= len(Columns())
// - the cell actually contains a nil value
//
// The returned value type depends on the View implementation and
// the underlying data source. Common types include:
// - string (for text-based sources like CSV)
// - numeric types (int, float64, etc.)
// - time.Time (for date/time values)
// - bool
// - custom types (for struct-based Views)
//
// Callers should use type assertions or reflection to work with
// the returned value. For more type-safe access, consider using
// ViewToStructSlice or SmartAssign functions.
Cell(row, col int) any
}
View is the central interface in the retable package that represents read-only tabular data in a uniform, type-agnostic way. It provides access to table metadata (title, columns) and cell data through a simple coordinate-based API.
View is designed as an in-memory abstraction - implementations should load all data before wrapping it as a View. This design choice eliminates the need for context parameters and error handling in the core View methods, simplifying the API.
Design Philosophy ¶
The View interface follows these principles:
- Immutability: Views are read-only; modifications create new Views
- Simplicity: No error returns from data access methods (data already in memory)
- Uniformity: All tabular data sources expose the same interface
- Composability: Views can be wrapped and transformed via decorator types
Coordinate System ¶
Views use zero-based indexing:
- Columns are numbered 0 to len(Columns())-1
- Rows are numbered 0 to NumRows()-1
Cell Values ¶
Cell values are returned as any (empty interface) and can be:
- Go primitives (int, string, bool, float64, etc.)
- Complex types (time.Time, custom structs, etc.)
- nil (for missing/null values or out-of-bounds access)
Common Implementations ¶
The package provides several View implementations:
- StringsView: Backed by [][]string (from CSV, text)
- StructRowsView: Backed by []StructType (reflection-based)
- AnyValuesView: Backed by [][]any (from SQL, mixed types)
- ReflectValuesView: Backed by [][]reflect.Value (advanced)
View Wrappers ¶
Several wrapper types transform Views without copying data:
- FilteredView: Row/column filtering and remapping
- DerefView: Automatic pointer dereferencing
- ExtraColsView: Horizontal concatenation
- ExtraRowsView: Vertical concatenation
- ViewWithTitle: Custom title override
Example Usage ¶
// Create a view from structs
type Person struct {
Name string
Age int
}
people := []Person{{"Alice", 30}, {"Bob", 25}}
view := NewStructRowsView("People", people, nil, nil)
// Access data
fmt.Println(view.Title()) // "People"
fmt.Println(view.Columns()) // ["Name", "Age"]
fmt.Println(view.NumRows()) // 2
fmt.Println(view.Cell(0, 0)) // "Alice"
fmt.Println(view.Cell(0, 1)) // 30
fmt.Println(view.Cell(2, 0)) // nil (out of bounds)
func NewStructRowsView ¶
NewStructRowsView creates a new StructRowsView for the given struct slice data.
This function is typically called by StructRowsViewer.NewView after determining the column names and field-to-column mapping through reflection and naming rules.
Parameters:
- title: The title for the table view
- columns: Slice of column names in display order
- indices: Maps struct field indices to column indices (or nil for 1:1 mapping)
- rows: reflect.Value containing a slice or array of structs
The indices parameter, when non-nil, must satisfy these constraints:
- Length equals the number of struct fields (exported, with embedded fields inlined)
- Each value is either -1 (field excluded) or a valid column index [0, len(columns))
- Each column index from 0 to len(columns)-1 appears exactly once
- No column index appears more than once
If indices represents a simple 1:1 mapping where indices[i] == i for all i, it will be optimized to nil internally.
Panics if:
- rows is not a slice or array
- indices violates the mapping constraints
- any column is unmapped
- any column is mapped more than once
Example:
// For struct: {Field0, Field1, Field2, Field3}
// To create columns: [Field1, Field3, Field0] (Field2 excluded)
indices := []int{2, 0, -1, 1}
view := NewStructRowsView("Data", []string{"Col0", "Col1", "Col2"}, indices, rowsValue)
func SingleCellView ¶
SingleCellView creates a View containing exactly one cell with a typed value.
This generic function creates a minimal 1x1 view (one row, one column) containing a single value of type T. It provides type safety and can be used to wrap scalar values as Views for compatibility with view-based APIs.
The resulting view has one row and one column. It implements both View and ReflectCellView interfaces.
Performance characteristics:
- Minimal memory footprint (one value + metadata)
- O(1) cell access
- No allocations for repeated access
When to use SingleCellView:
- Wrapping a single scalar value as a View
- Creating minimal test fixtures
- Representing single-value results in a tabular format
- Building view hierarchies where leaf nodes are single values
Parameters:
- title: The title of the view
- column: The name of the single column
- value: The value of type T to store in the single cell
Returns a View with one row and one column containing the provided value.
Example:
// Create a single-cell view with an integer
view := retable.SingleCellView("Count", "Total", 42)
fmt.Println(view.Title()) // Output: Count
fmt.Println(view.NumRows()) // Output: 1
fmt.Println(view.Cell(0, 0)) // Output: 42
// Create a single-cell view with a string
view := retable.SingleCellView("Status", "Message", "Success")
fmt.Println(view.Cell(0, 0)) // Output: Success
Thread safety: Immutable after creation (assuming T is not a reference type that is modified externally), safe for concurrent reads.
func SingleColView ¶
SingleColView creates a View containing a single column with typed data.
This generic function provides a type-safe way to create a one-column view from a slice of values. The generic type parameter T determines the type of values in the column, providing compile-time type safety.
The resulting view has one column and len(rows) rows, where each row contains one value from the rows slice. The view implements both View and ReflectCellView interfaces.
Performance characteristics:
- Zero-copy: directly wraps the provided slice
- O(1) cell access
- Memory efficient (no data duplication)
- Generic type instantiation overhead at compile time only
When to use SingleColView:
- Working with a single column of homogeneous typed data
- Converting a slice into a View for compatibility
- Building columnar data structures
- Type-safe single-column operations
Parameters:
- column: The name of the single column
- rows: Slice of values of type T, one per row
Returns a View with one column containing the provided values.
Example:
// Create a single-column view of integers
numbers := []int{10, 20, 30, 40}
view := retable.SingleColView("Value", numbers)
fmt.Println(view.NumRows()) // Output: 4
fmt.Println(view.Cell(2, 0)) // Output: 30
// Create a single-column view of strings
names := []string{"Alice", "Bob", "Charlie"}
view := retable.SingleColView("Name", names)
fmt.Println(view.Cell(1, 0)) // Output: Bob
Thread safety: Not thread-safe if the underlying rows slice is modified. The view does not copy the data, so concurrent modifications to the slice will be visible through the view.
func ViewWithTitle ¶
ViewWithTitle creates a new View that wraps a source View with a different title.
This function provides a way to change the title of an existing view without modifying the original or copying the data. The returned view delegates all operations to the source view except for Title(), which returns the provided title string.
The wrapper implements both View and ReflectCellView interfaces, providing transparent access to the underlying data while presenting a different title.
Performance characteristics:
- Zero-copy: no data duplication
- O(1) overhead for all operations (simple delegation)
- Minimal memory footprint (wrapper object only)
When to use ViewWithTitle:
- Need to display the same data with different titles in different contexts
- Building view pipelines where title changes are required
- Implementing view transformations that only affect metadata
- Creating aliases or alternative presentations of existing views
Parameters:
- source: The View to wrap (will be converted to ReflectCellView)
- title: The new title to use for the wrapped view
Returns a View that has the specified title but otherwise behaves like source.
Example:
original := retable.NewStringsView("Original Title", data, "A", "B")
renamed := retable.ViewWithTitle(original, "New Title")
fmt.Println(renamed.Title()) // Output: New Title
fmt.Println(renamed.Cell(0, 0)) // Delegates to original
fmt.Println(original.Title()) // Output: Original Title (unchanged)
Thread safety: Safe for concurrent reads if the source view is safe for concurrent reads. The wrapper is immutable after creation.
type Viewer ¶
type Viewer interface {
// NewView creates a View with the specified title from the given table data.
//
// The table parameter should contain tabular data in a format that this
// Viewer implementation recognizes. Different Viewer implementations accept
// different types:
// - StringsViewer accepts [][]string
// - StructRowsViewer accepts []StructType or []*StructType
//
// Returns an error if:
// - table is not a supported type for this Viewer
// - table is nil or empty (implementation-dependent)
// - the data structure is invalid or malformed
//
// The title parameter becomes the result of the View's Title() method.
// It can be an empty string if no title is needed.
NewView(title string, table any) (View, error)
}
Viewer is a factory interface for creating Views from arbitrary table data. Viewers are responsible for recognizing specific data structures (like [][]string or []StructType) and wrapping them as View implementations.
Purpose ¶
The Viewer interface provides a uniform way to create Views from different data representations without needing to know the exact View type to construct. This is especially useful in generic code that works with multiple data formats.
Standard Implementations ¶
- StringsViewer: Creates views from [][]string (CSV, text data)
- StructRowsViewer: Creates views from struct slices via reflection
Example Usage ¶
// Using StringsViewer
viewer := StringsViewer{FirstRowIsHeader: true}
data := [][]string{
{"Name", "Age"},
{"Alice", "30"},
{"Bob", "25"},
}
view, err := viewer.NewView("People", data)
// Using StructRowsViewer
type Person struct {
Name string
Age int
}
people := []Person{{"Alice", 30}, {"Bob", 25}}
viewer := NewStructRowsViewer(nil)
view, err := viewer.NewView("People", people)
Custom Viewers ¶
Implement this interface to create Views from custom data structures:
type MyViewer struct{}
func (v MyViewer) NewView(title string, table any) (View, error) {
myData, ok := table.(MyDataType)
if !ok {
return nil, fmt.Errorf("expected MyDataType, got %T", table)
}
return myViewFromData(title, myData), nil
}
Source Files
¶
- anyvaluesview.go
- assign.go
- cellformatter.go
- config.go
- derefview.go
- extracolsfuncview.go
- extracolsview.go
- extrarowsview.go
- filteredview.go
- formatstrings.go
- formatter.go
- options.go
- parser.go
- reflecttypecellformatter.go
- reflectvaluesview.go
- scanner.go
- singlecolview.go
- stringsview.go
- stringsviewer.go
- structfieldnaming.go
- structrowsview.go
- structrowsviewer.go
- utils.go
- view.go
- viewer.go
- viewtostructslice.go
- viewwithtitle.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package csvtable provides CSV parsing, writing, and format detection functionality with support for various encodings, separators, and RFC 4180 compliance.
|
Package csvtable provides CSV parsing, writing, and format detection functionality with support for various encodings, separators, and RFC 4180 compliance. |
|
exceltable
module
|
|
|
Package htmltable provides functionality for writing tables as HTML.
|
Package htmltable provides functionality for writing tables as HTML. |
|
Package sqltable provides a virtual SQL database driver that allows querying in-memory retable.View data structures using database/sql and SQL-like queries.
|
Package sqltable provides a virtual SQL database driver that allows querying in-memory retable.View data structures using database/sql and SQL-like queries. |