mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
149 lines
3.5 KiB
Go
149 lines
3.5 KiB
Go
package input
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type fixedWidthSplitter struct {
|
|
// Valid increasing indexes
|
|
indexes []int
|
|
}
|
|
|
|
// NewFixedWidthSplitter creates a new fixed-width field splitter based on the spec.
|
|
//
|
|
// The spec parameter supports these formats:
|
|
//
|
|
// widths:w1,w2,w3... : Split by explicit widths for each column. Omit last one for variable width ending (e.g., 4,4,5)
|
|
// left-align : Parse header with left-aligned fields
|
|
// left-align-multi-word : Parse header with multi-word support
|
|
// right-align : Parse header with right-aligned fields
|
|
// right-align-multi-word : Parse header with right-aligned fields and multi-word support
|
|
//
|
|
// For mult-word cases, the column headers can be made up of multiple words
|
|
// e.g. "Seq No". Adjacent columns should be seperated by at least two spaces
|
|
func NewFixedWidthSplitter(spec, referenceRow string) (*fixedWidthSplitter, error) {
|
|
var indexes []int
|
|
|
|
if spec[:7] == "widths:" {
|
|
var err error
|
|
indexes, err = parseWidths(spec[7:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else if spec == "left-align" {
|
|
indexes = parseLeftAlign(referenceRow, false)
|
|
} else if spec == "left-align-multi-word" {
|
|
indexes = parseLeftAlign(referenceRow, true)
|
|
} else if spec == "right-align" {
|
|
indexes = parseRightAlign(referenceRow, false)
|
|
} else if spec == "right-align-multi-word" {
|
|
indexes = parseRightAlign(referenceRow, true)
|
|
} else {
|
|
return nil, fmt.Errorf("unknown spec: %v", spec)
|
|
}
|
|
return &fixedWidthSplitter{indexes: indexes}, nil
|
|
|
|
}
|
|
|
|
func (sp *fixedWidthSplitter) Split(line string) []string {
|
|
return split(line, sp.indexes)
|
|
}
|
|
|
|
func parseWidths(widthsStr string) ([]int, error) {
|
|
var indexes []int
|
|
if len(widthsStr) == 0 {
|
|
return indexes, nil
|
|
}
|
|
widths := strings.Split(widthsStr, ",")
|
|
pos := 0
|
|
for _, w := range widths {
|
|
width, err := strconv.Atoi(strings.TrimSpace(w))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid width: %v, error: %w", w, err)
|
|
}
|
|
if width <= 0 {
|
|
return nil, fmt.Errorf("not a positive width: %v", w)
|
|
}
|
|
pos += width
|
|
indexes = append(indexes, pos)
|
|
}
|
|
return indexes, nil
|
|
}
|
|
|
|
func parseLeftAlign(referenceRow string, allowMultiWord bool) []int {
|
|
var indexes []int
|
|
inWord := true
|
|
firstSpace := false //Used for multi word. True on the first space after a non-space
|
|
for i, c := range referenceRow {
|
|
if c != ' ' {
|
|
if !inWord {
|
|
indexes = append(indexes, i)
|
|
}
|
|
inWord = true
|
|
} else {
|
|
if allowMultiWord {
|
|
if firstSpace {
|
|
inWord = false
|
|
firstSpace = false
|
|
} else {
|
|
firstSpace = true
|
|
// inWord = true // already true
|
|
}
|
|
} else {
|
|
inWord = false
|
|
}
|
|
}
|
|
}
|
|
return indexes
|
|
}
|
|
|
|
func parseRightAlign(referenceRow string, allowMultiWord bool) []int {
|
|
var indexes []int
|
|
inWord := false
|
|
firstSpace := false //Used for multi word. True on the first space after a non-space
|
|
for i, c := range referenceRow {
|
|
if c != ' ' {
|
|
inWord = true
|
|
firstSpace = false
|
|
} else {
|
|
if inWord {
|
|
if allowMultiWord {
|
|
firstSpace = true
|
|
} else {
|
|
indexes = append(indexes, i)
|
|
}
|
|
} else {
|
|
if allowMultiWord && firstSpace {
|
|
indexes = append(indexes, i-1)
|
|
firstSpace = false
|
|
}
|
|
}
|
|
inWord = false
|
|
}
|
|
}
|
|
return indexes
|
|
}
|
|
|
|
func split(line string, indexes []int) []string {
|
|
var result []string
|
|
if len(indexes) == 0 {
|
|
result = append(result, line)
|
|
return result
|
|
}
|
|
si := 0
|
|
l := len(line)
|
|
for _, idx := range indexes {
|
|
if idx > l {
|
|
break
|
|
}
|
|
result = append(result, line[si:idx])
|
|
si = idx
|
|
}
|
|
rest := line[si:]
|
|
if rest != "" {
|
|
result = append(result, rest)
|
|
}
|
|
return result
|
|
}
|