added transpose function (-T + -R)

This commit is contained in:
2025-01-12 19:28:52 +01:00
committed by T.v.Dein
parent 8792c5a40f
commit 4d894a728b
9 changed files with 271 additions and 134 deletions

View File

@@ -44,10 +44,10 @@ func matchPattern(conf cfg.Config, line string) bool {
* more filters match on a row, it will be kept, otherwise it will be
* excluded.
*/
func FilterByFields(conf cfg.Config, data Tabdata) (Tabdata, bool, error) {
func FilterByFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
if len(conf.Filters) == 0 {
// no filters, no checking
return Tabdata{}, false, nil
return nil, false, nil
}
newdata := data.CloneEmpty()
@@ -75,7 +75,44 @@ func FilterByFields(conf cfg.Config, data Tabdata) (Tabdata, bool, error) {
}
}
return newdata, true, nil
return &newdata, true, nil
}
/*
* Transpose fields using search/replace regexp.
*/
func TransposeFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
if len(conf.UseTransposers) == 0 {
// nothing to be done
return nil, false, nil
}
newdata := data.CloneEmpty()
transposed := false
for _, row := range data.entries {
transposedrow := false
for idx := range data.headers {
transposeidx, hasone := findindex(conf.UseTransposeColumns, idx+1)
if hasone {
row[idx] =
conf.UseTransposers[transposeidx].Search.ReplaceAllString(
row[idx],
conf.UseTransposers[transposeidx].Replace,
)
transposedrow = true
}
}
if transposedrow {
// also apply -v
newdata.entries = append(newdata.entries, row)
transposed = true
}
}
return &newdata, transposed, nil
}
/* generic map.Exists(key) */

View File

@@ -153,8 +153,8 @@ func TestFilterByFields(t *testing.T) {
t.Errorf("PrepareFilters returned error: %s", err)
}
data, _, _ := FilterByFields(conf, data)
if !reflect.DeepEqual(data, inputdata.expect) {
data, _, _ := FilterByFields(conf, &data)
if !reflect.DeepEqual(*data, inputdata.expect) {
t.Errorf("Filtered data does not match expected data:\ngot: %+v\nexp: %+v", data, inputdata.expect)
}
})

View File

@@ -40,6 +40,16 @@ func contains(s []int, e int) bool {
return false
}
func findindex(s []int, e int) (int, bool) {
for i, a := range s {
if a == e {
return i, true
}
}
return 0, false
}
// validate the consitency of parsed data
func ValidateConsistency(data *Tabdata) error {
expectedfields := len(data.headers)
@@ -57,13 +67,44 @@ func ValidateConsistency(data *Tabdata) error {
// parse columns list given with -c, modifies config.UseColumns based
// on eventually given regex
func PrepareColumns(conf *cfg.Config, data *Tabdata) error {
if conf.Columns == "" {
return nil
// -c columns
usecolumns, err := PrepareColumnVars(conf.Columns, data)
if err != nil {
return err
}
for _, use := range strings.Split(conf.Columns, ",") {
conf.UseColumns = usecolumns
return nil
}
func PrepareTransposerColumns(conf *cfg.Config, data *Tabdata) error {
// -T columns
usetransposecolumns, err := PrepareColumnVars(conf.TransposeColumns, data)
if err != nil {
return err
}
conf.UseTransposeColumns = usetransposecolumns
// verify that columns and transposers match and prepare transposer structs
if err := conf.PrepareTransposers(); err != nil {
return err
}
return nil
}
func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
if columns == "" {
return nil, nil
}
usecolumns := []int{}
for _, use := range strings.Split(columns, ",") {
if len(use) == 0 {
return fmt.Errorf("could not parse columns list %s: empty column", conf.Columns)
return nil, fmt.Errorf("could not parse columns list %s: empty column", columns)
}
usenum, err := strconv.Atoi(use)
@@ -71,15 +112,15 @@ func PrepareColumns(conf *cfg.Config, data *Tabdata) error {
// might be a regexp
colPattern, err := regexp.Compile(use)
if err != nil {
msg := fmt.Sprintf("Could not parse columns list %s: %v", conf.Columns, err)
msg := fmt.Sprintf("Could not parse columns list %s: %v", columns, err)
return errors.New(msg)
return nil, errors.New(msg)
}
// find matching header fields
for i, head := range data.headers {
if colPattern.MatchString(head) {
conf.UseColumns = append(conf.UseColumns, i+1)
usecolumns = append(usecolumns, i+1)
}
}
} else {
@@ -87,27 +128,28 @@ func PrepareColumns(conf *cfg.Config, data *Tabdata) error {
// a colum spec is not a number, we process them above
// inside the err handler for atoi(). so only add the
// number, if it's really just a number.
conf.UseColumns = append(conf.UseColumns, usenum)
usecolumns = append(usecolumns, usenum)
}
}
// deduplicate: put all values into a map (value gets map key)
// thereby removing duplicates, extract keys into new slice
// and sort it
imap := make(map[int]int, len(conf.UseColumns))
for _, i := range conf.UseColumns {
imap := make(map[int]int, len(usecolumns))
for _, i := range usecolumns {
imap[i] = 0
}
conf.UseColumns = nil
// fill with deduplicated columns
usecolumns = nil
for k := range imap {
conf.UseColumns = append(conf.UseColumns, k)
usecolumns = append(usecolumns, k)
}
sort.Ints(conf.UseColumns)
sort.Ints(usecolumns)
return nil
return usecolumns, nil
}
// prepare headers: add numbers to headers

View File

@@ -175,13 +175,27 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
}
// filter by field filters, if any
filtereddata, changed, err := FilterByFields(conf, data)
filtereddata, changed, err := FilterByFields(conf, &data)
if err != nil {
return data, fmt.Errorf("failed to filter fields: %w", err)
}
if changed {
data = filtereddata
data = *filtereddata
}
// transpose if demanded
if err := PrepareTransposerColumns(&conf, &data); err != nil {
return data, err
}
modifieddata, changed, err := TransposeFields(conf, &data)
if err != nil {
return data, fmt.Errorf("failed to transpose fields: %w", err)
}
if changed {
data = *modifieddata
}
// apply user defined lisp process hooks, if any