model

package
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 4, 2025 License: MIT Imports: 8 Imported by: 0

Documentation

Index

Examples

Constants

View Source
const JSON_NULL = "null"

Variables

View Source
var (
	ErrLongTemplate      = errors.New("ruler template is too long")
	ErrMissingHorizontal = errors.New("missing required horizontal character (`-` or `=`)")
	ErrBadVertical       = errors.New("bad vertical ruler character")
	ErrBadHorizontal     = errors.New("bad horizontal ruler character")
)

Functions

func LastNumericIndex added in v0.3.2

func LastNumericIndex(field string) int

LastNumericIndex finds the index of the right bound of the last number within a string.

Assumption:

  • digits and the decimal point are simple ASCII values with ordinal values < 0x7f
  • no need to worry about UTF-8 runes (runes with ordinal values > 0x7f are encoded such that the 1st bit is always set in every byte of the rune's representation)

Process:

  • start at the end of the string
  • record the index of the last digit encountered
  • stop as soon as a '.' is found

Return:

  • -1: no numeric value was found (width = total number of runes in the string)
  • the index position of the decimal point
  • or the index of the last digit + 1

func LookupFunc

func LookupFunc[T any](m *FieldMap) func(fields []T, name string) (value T)

LookupFunc returns a function for lazily locating a field in a generic slice.

The returned function, can be used to reliably get a value, without the risk of an out-of-bounds index.

The motivation behind this is to enable a user friendly method of accessing columns of data by name, i.e. over many rows, regardless of whether an individual row is complete or not.

e.g.

// common field map (index of name => field number)
m := NewFieldMap( strings.Fields( "one two three" ) )

ints := [][]int{
    {1,2,3},     // one field per name
    {1,2,3,4,5}, // a longer slice
    {1},         // a shorter slice
    {},          // an empty slice
    nil,         // a missing slice
}

lookupInt    := LookupFunc[int](m)
for _, row := range ints {
    v1 := lookupInt(row,"one")
}

structs := [][]*SomeStruct{
    ...,    // as above, but with structs instead of ints
}

lookupStruct := LookupFunc[*SomeStruct](m)
for _, row := range structs {
    v3 := lookupStruct(row,"three")
}

func VersionIndex added in v0.3.2

func VersionIndex(field string) int

VersionIndex finds the index of the right bound of the first number within a string.

Assumption:

  • digits and the decimal point are simple ASCII values with ordinal values < 0x7f
  • no need to worry about UTF-8 runes (runes with ordinal values > 0x7f are encoded such that the 1st bit is always set in every byte of the rune's representation)

Process:

  • start at the start of the string
  • find the first '.' following a digit

Return:

  • -1: no numeric value was found (width = total number of runes in the string)
  • the index position of the decimal point
  • or the index of the last digit + 1

Types

type ColumnFlags

type ColumnFlags byte

ColumnFlags flags are used to specify how a column should be prepared for rendering.

- Alignment - Sorting (incl. sort priority, between 0 and 7)

const (
	UnconfiguredFlags  ColumnFlags = 0x00 // .... .... - an unconfigured column: default alignment, no sorting
	UnalignedFlag      ColumnFlags = 0x00 // .... .000 - default alignment (left aligned, but without a ruler hint)
	AlignRightFlag     ColumnFlags = 0x01 // .... .001 - align text to the right of the column
	AlignLeftFlag      ColumnFlags = 0x02 // .... .010 - align text to the left of the column
	AlignCenterFlag    ColumnFlags = 0x03 // .... .011 - center text within the column
	AlignNumericFlag   ColumnFlags = 0x04 // .... .100 - align numbers around their decimal point, otherwise right aligned
	AlignVersionFlag   ColumnFlags = 0x07 // .... .111 - align on first point, sort using integer numbers only
	AlignmentMask      ColumnFlags = 0x07 // .... .111
	SortColumnFlag     ColumnFlags = 0x08 // .... 1...
	SortAscendingFlag  ColumnFlags = 0x00 // ...0 ....
	SortDescendingFlag ColumnFlags = 0x10 // ...1 ....
	SortDirectionMask  ColumnFlags = 0x10 // ...1 ....
	SortFlagsMask      ColumnFlags = 0x18 // ...1 1...
	SortPriorityMask   ColumnFlags = 0xe0 // 111. .... - 1 .. 7 only

)

func (ColumnFlags) AlignmentString

func (f ColumnFlags) AlignmentString() string

func (ColumnFlags) CmpSign

func (f ColumnFlags) CmpSign() int

func (*ColumnFlags) DisableSort

func (f *ColumnFlags) DisableSort()

func (*ColumnFlags) EnableSort

func (f *ColumnFlags) EnableSort()

func (ColumnFlags) IsAscendingOrder

func (f ColumnFlags) IsAscendingOrder() bool

func (ColumnFlags) IsCenterAligned

func (f ColumnFlags) IsCenterAligned() bool

func (ColumnFlags) IsConfigured

func (f ColumnFlags) IsConfigured() bool

func (ColumnFlags) IsDefaultAligned

func (f ColumnFlags) IsDefaultAligned() bool

func (ColumnFlags) IsDescendingOrder

func (f ColumnFlags) IsDescendingOrder() bool

func (ColumnFlags) IsLeftAligned

func (f ColumnFlags) IsLeftAligned() bool

func (ColumnFlags) IsNumberAligned

func (f ColumnFlags) IsNumberAligned() bool

func (ColumnFlags) IsRightAligned

func (f ColumnFlags) IsRightAligned() bool

func (ColumnFlags) IsSorted

func (f ColumnFlags) IsSorted() bool

func (ColumnFlags) IsVersionAligned added in v0.3.2

func (f ColumnFlags) IsVersionAligned() bool

func (ColumnFlags) MarshalJSON

func (f ColumnFlags) MarshalJSON() ([]byte, error)

MarshalJSON encodes a set of ColumnFlags to JSON.

Used mostly for encoding columns as JSON for tests.

func (*ColumnFlags) ReverseSort

func (f *ColumnFlags) ReverseSort()

func (*ColumnFlags) SetAlignment

func (f *ColumnFlags) SetAlignment(flags ColumnFlags)

func (*ColumnFlags) SetSort

func (f *ColumnFlags) SetSort(s ColumnFlags)

func (*ColumnFlags) SetSortPriority

func (f *ColumnFlags) SetSortPriority(prio int)

SetSortPriority sets the priority used when determining which columns to compare, and in what order, when sorting rows

Setting a column's priority does not enable or disable a column's sort status. (Setting a columns priority should always be compbined with EnableSort())

func (ColumnFlags) SortPriority

func (f ColumnFlags) SortPriority() int

SortPriority returns the sort-priority of the column, if the column is sorted.

Invariants:

  • only values between 0 and 7 will be returns
  • 0 indicates 'no special priority' or that the column has no sorting preference

func (ColumnFlags) SortString

func (f ColumnFlags) SortString() string

func (ColumnFlags) String

func (f ColumnFlags) String() string

String returns a string representing the alignment and sorting configuration of the column.

The string will have 0, 1, 2 or 3 positional characters for the alignment, sorting direction and sorting priority.

  • "" indicates a default column without any alignment or sorting preferences
  • alignment character: "l" - left aligned "r" - right aligned "c" - center aligned "n" - numerically aligned
  • sorting direction character: "^" - sort column from lowest to highest values "V" - sort column from highest to lowest values
  • sorting priority character: [0…7] - comparison priority of the column when sorting

e.g.: "nv2" would indicate "numeric alignment", "sorted in descending order" and "compared with priority 3 while sorting"

func (*ColumnFlags) UnmarshalJSON

func (f *ColumnFlags) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a json string into a set of ColumnFlags.

Used mostly for decoding columns from JSON for tests.

type ColumnWidth

type ColumnWidth struct {
	Bytes int // for memory allocation only
	Chars int // for left, right, center padding
	Left  int // for padding to left of decimal point
	Right int // for padding to right of decimal point
}

Column widths depend on the column's alignment flags

func (*ColumnWidth) ScanField

func (cw *ColumnWidth) ScanField(field string, cf ColumnFlags)

func (ColumnWidth) String

func (cw ColumnWidth) String() string

type Config

type Config struct {
	LocaleTag          Nullable[string]      `json:"locale,omitempty"`
	AlignDocument      Nullable[bool]        `json:"align,omitempty"` // json encoding for debugging only
	SquashEmptyColumns Nullable[bool]        `json:"squash,omitempty"`
	Sort               Nullable[bool]        `json:"sort,omitempty"`
	Prefix             Nullable[string]      `json:"prefix,omitempty"`
	Rulers             []*Ruler              `json:"rulers,omitempty"` // nil = use rulers from table
	ColumnFlagsMask    ColumnFlags           `json:"cols,omitempty"`   // inverse flag: 0 == all flags allowed, 0xff == no flags allowed
	SortColumns        []SortColumn          `json:"-"`
	ColumnFlags        SliceMap[ColumnFlags] `json:"-"`
	TerminateEarly     bool                  `json:"-"` // indicate that main() should not actually process any input
}

TODO: 2025-02-24 simplify - config should consist of "simple" values, bool, int, string, []string...

func (*Config) AppendSortColumn

func (cfg *Config) AppendSortColumn(arg string)

func (*Config) String

func (cfg *Config) String() string

String returns a multi-line, formatted representation of the current configuration

type Document

type Document struct {
	// contains filtered or unexported fields
}

Document represents a single text file which may contain any number of tables.

This is a model object without any business login.

To Encode or Decode a document, see [document.Encoder] and [document.Decoder]

func (*Document) AppendRow

func (doc *Document) AppendRow(prefix string, row []string)

AppendRow appends a row to the end of the current table.

A new table will be added to the document if necessary.

func (*Document) AppendRuler

func (doc *Document) AppendRuler(prefix, template string, colFlags []ColumnFlags)

AppendRuler appends a ruler to the end of the current table.

A new table will be added to the document if necessary.

func (*Document) AppendText

func (doc *Document) AppendText(line string)

AppendText appends a line of text to the end of the document.

Text lines are in no-way modified and are reproduced verbatim.

Text lines separate multiple tables within a document.

func (*Document) Items

func (doc *Document) Items() []*DocumentItem

Items returns the slice of all document items within the document

func (*Document) Tables

func (doc *Document) Tables() []*Table

Tables returns the slice of tables within the document

type DocumentItem

type DocumentItem struct {
	*Text
	*Table
}

DocumentItem represents a single block of text or a table within a document.

If both Text and Table are not nil, then the Text is positioned before the table.

type FieldMap

type FieldMap struct {
	// contains filtered or unexported fields
}

FieldMap provides a mapping of field names to their position in a list.

This is used to identify a field in a row, based on a column name.

func NewFieldMap

func NewFieldMap(names []string) *FieldMap

NewFieldMap returns a new FieldMap for an ordered list of field names.

func (*FieldMap) Find

func (m *FieldMap) Find(name string) (pos int, ok bool)

Find returns the position of a matching string within the FieldMap.

Multiple attempts may be made to find the most appropriate match.

If a name is duplicated, only the first instance will be returned.

If none of the attempts succeed, ok will be false.

Search criteria, in order:

  • exact cache match (each search result is memoised for performance)
  • exact match
  • integer match (always returns ok if >= 1, assumes an infinitely large set of fields)
  • case-insensitive exact match
  • case-insensitive prefix match
Example

ExampleFieldMap demonstrates how a FieldMap can be used to identify columns of a PSV table

package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	fields := []string{"foo", "food", "Footnote", "", "1", "2"}

	fm := model.NewFieldMap(fields)

	testCases := []struct{ name, desc string }{
		{"foo", "exact match"},
		{"food", "exact match"},
		{"foot", "case insensitive prefix"},
		{"Foot", "case sensitive prefix"},
		{"FO", "first case-insensitive prefix"},
		{"Fo", "first case-sensitive prefix"},
		{"", "empty name - only matches if an empty field exists"},
		// searching fields via numbers
		{"1", "match numeric name"},
		{"2", "match numeric name"},
		{"+2", "force field number. +2 does not match field name '2', but is a positive integer"},
		{"3", "fictitious field"},
		{"7", "fictitious field"},
		// numeric boundaries
		{"0", "ignored - field numbers start at 1"},
		{"7", "fictitious field"},
	}

	fmt.Printf("Fields: %q\n", fields)
	for _, tc := range testCases {
		pos, ok := fm.Find(tc.name)
		fmt.Printf("find %-6q => %d %v (%s)\n", tc.name, pos, ok, tc.desc)
	}

}
Output:

Fields: ["foo" "food" "Footnote" "" "1" "2"]
find "foo"  => 0 true (exact match)
find "food" => 1 true (exact match)
find "foot" => 2 true (case insensitive prefix)
find "Foot" => 2 true (case sensitive prefix)
find "FO"   => 0 true (first case-insensitive prefix)
find "Fo"   => 2 true (first case-sensitive prefix)
find ""     => 3 true (empty name - only matches if an empty field exists)
find "1"    => 4 true (match numeric name)
find "2"    => 5 true (match numeric name)
find "+2"   => 1 true (force field number. +2 does not match field name '2', but is a positive integer)
find "3"    => 2 true (fictitious field)
find "7"    => 6 true (fictitious field)
find "0"    => 0 false (ignored - field numbers start at 1)
find "7"    => 6 true (fictitious field)

type Nullable

type Nullable[T comparable] struct {
	// contains filtered or unexported fields
}

func NewNullable

func NewNullable[T comparable](value ...T) Nullable[T]

func (*Nullable[T]) Clear

func (n *Nullable[T]) Clear()

Clear marks the value as NULL, i.e. not valid

func (*Nullable[T]) Coalesce

func (n *Nullable[T]) Coalesce(in ...Nullable[T])

Coalesce updates the value from another Nullable of the same type, but only if the other Nullable has a non-null value.

func (*Nullable[T]) CoalesceValue

func (n *Nullable[T]) CoalesceValue(values ...T)

Coalesce updates the value from another Nullable of the same type, but only if the other Nullable has a non-null value.

func (Nullable[T]) Get

func (n Nullable[T]) Get() (value T, IsSet bool)

Get returns the value and its IsSet flag as a tupel.

The value MUST be ignored if the IsSet flag is false.

See also Value()

func (Nullable[T]) IsNull

func (n Nullable[T]) IsNull() bool

IsNull returns true if the value is undefined

func (Nullable[T]) IsSet

func (n Nullable[T]) IsSet() bool

IsSet returns true if the value is defined

func (Nullable[T]) MarshalJSON

func (n Nullable[T]) MarshalJSON() ([]byte, error)

MarshalJSON encodes the nullable value either in its native JSON form, or as a JSON literal `null`

func (*Nullable[T]) Set

func (n *Nullable[T]) Set(value T)

Set changes the stored value, and marks the value as valid

func (*Nullable[T]) UnmarshalJSON

func (n *Nullable[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string into a nullable value.

If the JSON string is a literal `null`, then the value will be set to NULL (i.e. not valid)

Otherwise, the value will be unmarshalled normally and the resulting value stored as a valid value.

func (Nullable[T]) Value

func (n Nullable[T]) Value() (value T)

Value returns just the object's value, or the type's zero value if the object is not set.

See also IsSet() and Get()

type Ruler

type Ruler struct {
	Pos        int  // this ruler's desired position in its table
	Border     byte // outer vertical line: `|` or `+`
	Padding    byte // horizontal padding:  `-`, `=` or ` `
	Horizontal byte // horizontal line:     `-` or `=`
	Separator  byte // inner vertical line: `|` or `+`
}

Ruler represents a horizontal separator line within a table.

Rulers grow and shrink depending on the width of the data in each column.

Usage

Building, e.g. while parsing a document

r := &Ruler{}
r.Border = …
r.Padding = …
r.Horizontal = …
r.Separator = …
err := r.Validate()

Creation from a template

r, err := NewRuler(template)

Ruler Template structure

The template structure was chosen to allow it to be shortened without loss of detail, by being in the same order that they would appear in an actual ruler, while ignoring duplicates.

Template                    `| -+`
                             ^^^^
                             ||||
outer border               --+|||
padding                    ---+||
horizontal line            ----+|
internal column separator  -----+

Example Templates

Template    Example Ruler
`|-`        `|-----|----------|-------|`
`|=`        `|=====|==========|=======|`
`| -`       `| --- | -------- | ----- |` (default)
`| -+`      `| --- + -------- + ----- |`
`+ -`       `+ --- + -------- + ----- +`
`+-`        `+-----+----------+-------+`
Example (Zero_value_invalid)
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	// a zero-value ruler is not usable if not validated
	r := model.Ruler{}

	fmt.Printf("r := model.Ruler{} // without validation\n")
	fmt.Printf("Template:          %q\n", r.Template())
	fmt.Printf("Border Char:       %q\n", r.Border)
	fmt.Printf("Padding Char:      %q\n", r.Padding)
	fmt.Printf("Horizontal Char:   %q\n", r.Horizontal)
	fmt.Printf("Separator Char:    %q\n", r.Separator)

}
Output:

r := model.Ruler{} // without validation
Template:          "\x00\x00\x00\x00"
Border Char:       '\x00'
Padding Char:      '\x00'
Horizontal Char:   '\x00'
Separator Char:    '\x00'
Example (Zero_value_validated)
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	// A zero-value ruler IS usable after validation
	r := model.Ruler{}
	err := r.Validate()

	fmt.Printf("r := model.Ruler{}\n")
	fmt.Printf("r.Validate()       // with validation\n")
	fmt.Printf("Validation Error:  %v\n", err)
	fmt.Printf("Template:          %q\n", r.Template())
	fmt.Printf("Border Char:       %q\n", r.Border)
	fmt.Printf("Padding Char:      %q\n", r.Padding)
	fmt.Printf("Horizontal Char:   %q\n", r.Horizontal)
	fmt.Printf("Separator Char:    %q\n", r.Separator)

}
Output:

r := model.Ruler{}
r.Validate()       // with validation
Validation Error:  <nil>
Template:          "| -|"
Border Char:       '|'
Padding Char:      ' '
Horizontal Char:   '-'
Separator Char:    '|'

func NewRuler

func NewRuler(template string) (*Ruler, error)

NewRuler creates a new ruler from a template string. See Ruler for template construction rules.

The template must be <= 4 characters long.

The template will be normalised if it is too short. i.e., if missing...

  • the border and padding characters will be copied from the default template
  • the horizontal character will be copied from the padding character
  • the separator will be copied from the border

An error will be returned if the template is too long or if it contains invalid characters.

Example (Bad_templates)
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	// templates containing invalid characters
	badTemplates := []string{
		`x`,     // `x` does not represent a vertical or horizontal line
		`-`,     // `-` may only be used as a horizontal line - wrong position
		`+x`,    //
		`++`,    // `+` may only be used as a vertical character
		`+ x`,   //
		`+ |`,   // vertical line instead of a horizontal line
		`| =x`,  //
		`| ==`,  // horizontal line instead of a vertical line
		`| -|x`, // too long
	}

	for _, t := range badTemplates {
		_, err := model.NewRuler(t)
		fmt.Printf("r, err := NewRuler(%-7q) => error: %q\n", t, err)
	}

}
Output:

r, err := NewRuler("x"    ) => error: "bad vertical ruler character for border 'x'"
r, err := NewRuler("-"    ) => error: "bad vertical ruler character for border '-'"
r, err := NewRuler("+x"   ) => error: "bad horizontal ruler character for padding 'x'"
r, err := NewRuler("++"   ) => error: "bad horizontal ruler character for padding '+'"
r, err := NewRuler("+ x"  ) => error: "bad horizontal ruler character for horizontal line 'x'"
r, err := NewRuler("+ |"  ) => error: "bad horizontal ruler character for horizontal line '|'"
r, err := NewRuler("| =x" ) => error: "bad vertical ruler character for internal separators 'x'"
r, err := NewRuler("| ==" ) => error: "bad vertical ruler character for internal separators '='"
r, err := NewRuler("| -|x") => error: "ruler template is too long \"| -|x\" (max 4 characters)"
Example (Default)
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	r, err := model.NewRuler("")

	fmt.Printf("r, err := model.NewRuler(%q) // preferred default ruler\n", "")
	fmt.Printf("Default Validation Error: %v\n", err)
	fmt.Printf("Default Template:         %q\n", r.Template())
	fmt.Printf("Default Border Char:      %q\n", r.Border)
	fmt.Printf("Default Padding Char:     %q\n", r.Padding)
	fmt.Printf("Default Horizontal Char:  %q\n", r.Horizontal)
	fmt.Printf("Default Separator Char:   %q\n", r.Separator)

}
Output:

r, err := model.NewRuler("") // preferred default ruler
Default Validation Error: <nil>
Default Template:         "| -|"
Default Border Char:      '|'
Default Padding Char:     ' '
Default Horizontal Char:  '-'
Default Separator Char:   '|'
Example (Good_templates)
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	goodTemplates := []struct {
		template string
		notes    string
	}{
		{"", "default"},
		{"|", "overlaps with default"},
		{"+", "minimal replacement"},
		{"+-", "unpadded"},
		{"|=", "unpadded, emphasised"},
		{"| =", "emphasised"},
		{"+ -|", "custom"},
	}

	for _, t := range goodTemplates {
		r, err := model.NewRuler(t.template)
		if err != nil {
			fmt.Printf("r, err := NewRuler(%-6q) => unexpected error: %q\n", t.template, err)
			continue
		}
		fmt.Printf("r, err := NewRuler(%-6q) => %q %s\n", t.template, r.Template(), t.notes)
	}

}
Output:

r, err := NewRuler(""    ) => "| -|" default
r, err := NewRuler("|"   ) => "| -|" overlaps with default
r, err := NewRuler("+"   ) => "+ -+" minimal replacement
r, err := NewRuler("+-"  ) => "+--+" unpadded
r, err := NewRuler("|="  ) => "|==|" unpadded, emphasised
r, err := NewRuler("| =" ) => "| =|" emphasised
r, err := NewRuler("+ -|") => "+ -|" custom

func (Ruler) MarshalJSON

func (r Ruler) MarshalJSON() ([]byte, error)

MarshalJSON encodes the ruler to JSON.

Optimisations:

  • position is not included if 0
  • template is not included if default
  • only uses short keys: `{"p":…, "t":…}`

Used mostly for encoding rulers as JSON for tests.

func (*Ruler) String

func (r *Ruler) String() string

String returns a string representation of the ruler, consisting of its position in the table and its template

func (*Ruler) Template

func (r *Ruler) Template() string

Template returns the ruler's template as a string

func (*Ruler) UnmarshalJSON

func (r *Ruler) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a json string into a ruler.

Keys:

`p` or `pos`      for the ruler's position with the table
`t` or `template` for the ruler's template

Used mostly for decoding rulers from JSON for tests.

func (*Ruler) Validate

func (r *Ruler) Validate() error

Validate a ruler's template

  • missing characters are normalised, using the partial template, and the default as a fallback
  • vertical characters must match `|` or `+`
  • Horizontal characters must match '-', `=` or ` `

type SliceMap

type SliceMap[T comparable] struct {
	Data []T
	// contains filtered or unexported fields
}

SliceMap works like a map[int]T, but uses a slice as its underlying data structure.

This is only intended for "small" slices, where the additional cost of using a map is not worth it™ (for your value of "worth it")

According to my testing on an Apple M1, SliceMap is about 5 times faster for slices with on the order of 10 items, compared to using a normal map. This gain drops however with larger slices/maps.

- items can be added with any index - any index can be "retrieved", even if it does not exist in the SliceMap - getting unset items simply returns the type's zero-value

func (SliceMap[T]) Cap

func (s SliceMap[T]) Cap() int

Cap returns the most items currently maintained by the SliceMap

This has limited utility, because calling Set(1000,x) on an empty SliceMap will cause the SliceMap to have a capacity of 1000 even though the SliceMap only contains 1 actual value.

However, the behaviour for retrieving values is always the same, regardless of the actual capacity of the SliceMap's internal slice.

func (*SliceMap[T]) Delete

func (s *SliceMap[T]) Delete(i int)

Delete removes a value from the SliceMap (by setting it to the type's zero-value)

This is equivalent to delete

aMap[i]

func (SliceMap[T]) Get

func (s SliceMap[T]) Get(i int) (v T, isInRange bool)

Get returns an item from the SliceMap allong with an indication of whether the value is within the stored index range.

This is equivalent to v, ok := aMap[i]

func (*SliceMap[T]) Grow

func (s *SliceMap[T]) Grow(size int)

Grow assures that the SliceMap has room for at least a known number of elements.

Calling Grow is not required, but may provide better performance.

func (SliceMap[T]) Has

func (s SliceMap[T]) Has(i int) bool

Has returns true if the provided index is within the range of stored values.

This does NOT indicate whether the specific item was actually set because calling Set(1000,x) on an empty SliceMap will cause the Has to return true for all indexes between 0 and 1000

See also Nullable

func (SliceMap[T]) IsEmpty

func (s SliceMap[T]) IsEmpty() bool

IsEmpty returns true if the SliceMap contains no non-zero items.

func (*SliceMap[T]) Reset

func (s *SliceMap[T]) Reset()

Reset discards all items and releases the memory used by the SliceMap.

func (*SliceMap[T]) Set

func (s *SliceMap[T]) Set(i int, v ...T)

Set stores a value in the SliceMap

This is equivalent to

aMap[i] = v

If called with multiple values, then a series of values is set.

The following code blocks are equivalent:

sm.Set( 5, "a", "b", "c" )

sm.Set( 5, "a" )
sm.Set( 6, "b" )
sm.Set( 7, "c" )

aMap[5] = "a"
aMap[6] = "b"
aMap[7] = "c"

func (SliceMap[T]) Value

func (s SliceMap[T]) Value(i int) (v T)

Value returns the value previously set by Set(i,v)

If no value was set, or the index lies beyond the bounds of the underlying slice, then the type's zero value is returned.

type SortColumn

type SortColumn struct {
	Name       string // name of a column
	Descending bool   // default: Ascending
}

type Table

type Table struct {
	Prefix      Nullable[string] // prefix of first row in table (optional)
	ColumnFlags []ColumnFlags    // column configurations (optional)
	Rulers      []*Ruler         // rulers to intersperse between the tables data rows
	Rows        [][]string       // the table's data, without rulers

}

Table represents a single, 2-dimensional table of string data

Example (Lookup_fields_by_name)
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	tbl := &model.Table{}
	tbl.AppendRow([]string{"name", "score"})
	tbl.AppendRow([]string{"Adam", "6"})

	m := model.NewFieldMap(tbl.ColumnNames()) // generic index of name => field number
	value := model.LookupFunc[string](m)      // func to get field by name from []string
	for _, row := range tbl.DataRows() {
		name := value(row, "name")
		score := value(row, "score")
		fmt.Printf("name: %q, score: %s\n", name, score)
	}

}
Output:

name: "Adam", score: 6
Example (Set_prefix_once)
package main

import (
	"fmt"
	"strings"

	"codeberg.org/japh/psv/encoding/prefix"
	"codeberg.org/japh/psv/model"
)

func main() {
	input := `
        want indent of 8 spaces
    not 4 spaces
            and definitely not 12
`

	ind := &prefix.Matcher{}
	tbl := &model.Table{}
	for _, line := range strings.Split(input, "\n") {
		if line == "" {
			continue
		}
		p, _ := ind.SplitLine(line)
		tbl.Prefix.CoalesceValue(p)
	}

	fmt.Printf("final indent: %q (%d spaces)\n", tbl.Prefix.Value(), len(tbl.Prefix.Value()))

}
Output:

final indent: "        " (8 spaces)

func (*Table) AllRows

func (tbl *Table) AllRows() [][]string
Example
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	tbl := &model.Table{}
	tbl.AppendRow([]string{"name", "score"})
	tbl.AppendRow([]string{"Adam", "6"})

	for r, row := range tbl.AllRows() {
		fmt.Printf("%d: %q\n", r, row)
	}

}
Output:

0: ["name" "score"]
1: ["Adam" "6"]

func (*Table) AppendRow

func (tbl *Table) AppendRow(row []string)
Example

ExampleTable_AppendRow demonstrates how a table can be built by repeatedly appending rows of data.

Special Cases:

  • the number of columns can vary, the table will end up with the maximum number of columns found in any row.
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	tbl := &model.Table{}
	tbl.AppendRow([]string{"hi"})                 // 1 column of data
	tbl.AppendRow([]string{"hi", "back"})         // 2 columns of data
	tbl.AppendRow([]string{})                     // no data
	tbl.AppendRow([]string{"see", "ya", "later"}) // 3 columns
	tbl.AppendRow([]string{"bye"})                // 1 column

	fmt.Printf("table has %d rows\n", len(tbl.AllRows()))

}
Output:

table has 5 rows

func (*Table) AppendRuler

func (tbl *Table) AppendRuler(template string) error
Example
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	tbl := &model.Table{}
	tbl.AppendRuler("")                      // rulers can be placed anywhere, even before the first row of data
	tbl.AppendRuler("")                      // the same ruler can be re-used
	tbl.AppendRow([]string{"name", "score"}) // 1st row of data, typically column names
	tbl.AppendRuler("")                      // a ruler after the first row of data is required for Markdown
	tbl.AppendRow([]string{"Alice", "5"})    // normal data
	tbl.AppendRuler("")                      //
	tbl.AppendRow([]string{"Bob", "2"})      // normal data
	tbl.AppendRuler("")                      // trailing rulers are also no problem

	fmt.Printf("%d rulers\n", len(tbl.Rulers))
	fmt.Printf("%d rows\n", len(tbl.Rows))

}
Output:

5 rulers
3 rows

func (*Table) ColumnNames

func (tbl *Table) ColumnNames() []string
Example
package main

import (
	"fmt"

	"codeberg.org/japh/psv/model"
)

func main() {
	tbl := &model.Table{}
	tbl.AppendRow([]string{"name", "score"})
	tbl.AppendRow([]string{"Adam", "6"})

	names := tbl.ColumnNames()
	fmt.Printf("column names: %q\n", names)

}
Output:

column names: ["name" "score"]

func (*Table) DataRows

func (tbl *Table) DataRows() [][]string
Example
package main

import (
	"fmt"
	"strings"

	"codeberg.org/japh/psv/model"
)

func main() {
	row := strings.Fields // helper

	tbl := &model.Table{}
	tbl.AppendRow(row("name score"))
	tbl.AppendRow(row("Adam 6    "))

	for r, row := range tbl.DataRows() {
		fmt.Printf("%d: %q\n", r, row)
	}

}
Output:

0: ["Adam" "6"]

func (Table) String

func (tbl Table) String() string

String provides a string representation of the table (for debugging only)

type Text

type Text struct {
	Lines []string
}

Text is a collection of lines which appear between tables within a document.

Text lines are never modified in any way and are reproduced verbatim when marshaling a document.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL