From de31cb1fc8b379f98a1e054f2cb943f9de935ec9 Mon Sep 17 00:00:00 2001 From: Balki Date: Sun, 1 Mar 2026 16:25:17 +0000 Subject: [PATCH] Add fixed width support for pprint reader (#1999) * Add fixed width support for pprint reader * fix windows fail * fix windows fail 2 --- pkg/cli/option_parse.go | 20 ++ pkg/cli/option_types.go | 2 + pkg/input/fixed_width_splitter.go | 149 ++++++++++++++ pkg/input/fixed_width_splitter_test.go | 267 +++++++++++++++++++++++++ pkg/input/record_reader_pprint.go | 239 ++++++++++++++++++++++ test/cases/cli-fixed-width/0001/cmd | 1 + test/cases/cli-fixed-width/0001/experr | 0 test/cases/cli-fixed-width/0001/expout | 17 ++ test/cases/cli-fixed-width/0001/input | 4 + test/cases/cli-fixed-width/0002/cmd | 1 + test/cases/cli-fixed-width/0002/experr | 0 test/cases/cli-fixed-width/0002/expout | 17 ++ test/cases/cli-fixed-width/0002/input | 4 + test/cases/cli-fixed-width/0003/cmd | 1 + test/cases/cli-fixed-width/0003/experr | 0 test/cases/cli-fixed-width/0003/expout | 17 ++ test/cases/cli-fixed-width/0003/input | 7 + test/cases/cli-fixed-width/0004/cmd | 1 + test/cases/cli-fixed-width/0004/experr | 0 test/cases/cli-fixed-width/0004/expout | 21 ++ test/cases/cli-fixed-width/0004/input | 11 + test/cases/cli-fixed-width/0005/cmd | 1 + test/cases/cli-fixed-width/0005/experr | 0 test/cases/cli-fixed-width/0005/expout | 68 +++++++ test/cases/cli-fixed-width/0005/input | 10 + 25 files changed, 858 insertions(+) create mode 100644 pkg/input/fixed_width_splitter.go create mode 100644 pkg/input/fixed_width_splitter_test.go create mode 100644 test/cases/cli-fixed-width/0001/cmd create mode 100644 test/cases/cli-fixed-width/0001/experr create mode 100644 test/cases/cli-fixed-width/0001/expout create mode 100644 test/cases/cli-fixed-width/0001/input create mode 100644 test/cases/cli-fixed-width/0002/cmd create mode 100644 test/cases/cli-fixed-width/0002/experr create mode 100644 test/cases/cli-fixed-width/0002/expout create mode 100644 test/cases/cli-fixed-width/0002/input create mode 100644 test/cases/cli-fixed-width/0003/cmd create mode 100644 test/cases/cli-fixed-width/0003/experr create mode 100644 test/cases/cli-fixed-width/0003/expout create mode 100644 test/cases/cli-fixed-width/0003/input create mode 100644 test/cases/cli-fixed-width/0004/cmd create mode 100644 test/cases/cli-fixed-width/0004/experr create mode 100644 test/cases/cli-fixed-width/0004/expout create mode 100644 test/cases/cli-fixed-width/0004/input create mode 100644 test/cases/cli-fixed-width/0005/cmd create mode 100644 test/cases/cli-fixed-width/0005/experr create mode 100644 test/cases/cli-fixed-width/0005/expout create mode 100644 test/cases/cli-fixed-width/0005/input diff --git a/pkg/cli/option_parse.go b/pkg/cli/option_parse.go index 782f3a7f5..11eafac14 100644 --- a/pkg/cli/option_parse.go +++ b/pkg/cli/option_parse.go @@ -559,6 +559,26 @@ var PPRINTOnlyFlagSection = FlagSection{ *pargi += 1 }, }, + + { + name: "--fixed", + arg: "{string}", + help: "Fixed width specification. One of 'widths:,,...', left-align, left-align-multi-word, right-align, right-align-multi-word", + parser: func(args []string, argc int, pargi *int, options *TOptions) { + options.ReaderOptions.FixedWidthSpec = args[*pargi+1] + *pargi += 2 + }, + }, + + { + name: "--fw", + arg: "{string}", + help: "Shortcut for --fixed left-align-multi-word", + parser: func(args []string, argc int, pargi *int, options *TOptions) { + options.ReaderOptions.FixedWidthSpec = "left-align-multi-word" + *pargi += 1 + }, + }, }, } diff --git a/pkg/cli/option_types.go b/pkg/cli/option_types.go index 4a2761e5f..f8a023813 100644 --- a/pkg/cli/option_types.go +++ b/pkg/cli/option_types.go @@ -58,6 +58,8 @@ type TReaderOptions struct { BarredPprintInput bool IncrementImplicitKey bool + FixedWidthSpec string + CommentHandling TCommentHandling CommentString string diff --git a/pkg/input/fixed_width_splitter.go b/pkg/input/fixed_width_splitter.go new file mode 100644 index 000000000..c7e822fb0 --- /dev/null +++ b/pkg/input/fixed_width_splitter.go @@ -0,0 +1,149 @@ +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 +} diff --git a/pkg/input/fixed_width_splitter_test.go b/pkg/input/fixed_width_splitter_test.go new file mode 100644 index 000000000..7b53615ea --- /dev/null +++ b/pkg/input/fixed_width_splitter_test.go @@ -0,0 +1,267 @@ +package input + +import ( + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestReferenceSplitter(t *testing.T) { + + type testCase struct { + scenario string + referenceRow string + line string + expectedLeftSingle []string + expectedLeftMulti []string + expectedRightSingle []string + expectedRightMulti []string + } + + testCases := []testCase{ + { + scenario: "base left", + referenceRow: "Name Place Thing", + line: "Name Place Thing", + expectedLeftSingle: []string{"Name ", "Place ", "Thing"}, + expectedLeftMulti: []string{"Name ", "Place ", "Thing"}, + expectedRightSingle: []string{"Name", " Place", " Thing"}, + expectedRightMulti: []string{"Name", " Place", " Thing"}, + }, + { + scenario: "base right", + referenceRow: " Name Place Thing", + line: " Name Place Thing", + expectedLeftSingle: []string{" ", "Name ", "Place ", "Thing"}, + expectedLeftMulti: []string{" ", "Name ", "Place ", "Thing"}, + expectedRightSingle: []string{" Name", " Place", " Thing"}, + expectedRightMulti: []string{" Name", " Place", " Thing"}, + }, + { + scenario: "left with data row", + referenceRow: "Name Place Thing", + line: "JohnDoe Nyc Bottle", + expectedLeftSingle: []string{"JohnDoe ", "Nyc ", "Bottle"}, + expectedLeftMulti: []string{"JohnDoe ", "Nyc ", "Bottle"}, + expectedRightSingle: []string{"John", "Doe Nyc ", " Bottle"}, + expectedRightMulti: []string{"John", "Doe Nyc ", " Bottle"}, + }, + { + scenario: "left multi word", + referenceRow: "Name Last Seen Thing", + line: "Name Last Seen Thing", + expectedLeftSingle: []string{"Name ", "Last ", "Seen ", "Thing"}, + expectedLeftMulti: []string{"Name ", "Last Seen ", "Thing"}, + expectedRightSingle: []string{"Name", " Last", " Seen", " Thing"}, + expectedRightMulti: []string{"Name", " Last Seen", " Thing"}, + }, + { + scenario: "right multi word", + referenceRow: " Name Last Seen Thing", + line: " Name Last Seen Thing", + expectedLeftSingle: []string{" ", "Name ", "Last ", "Seen ", "Thing"}, + expectedLeftMulti: []string{" ", "Name ", "Last ", "Seen ", "Thing"}, + expectedRightSingle: []string{" Name", " Last", " Seen", " Thing"}, + expectedRightMulti: []string{" Name", " Last Seen", " Thing"}, + }, + { + scenario: "left multi word data row", + referenceRow: "Name Last Seen Thing", + line: "Max two days ago Bottle", + expectedLeftSingle: []string{"Max ", "two d", "ays ago ", "Bottle"}, + expectedLeftMulti: []string{"Max ", "two days ago ", "Bottle"}, + expectedRightSingle: []string{"Max ", " two ", "days ", "ago Bottle"}, + expectedRightMulti: []string{"Max ", " two days ", "ago Bottle"}, + }, + { + scenario: "right with data row", + referenceRow: " Name Place Thing", + line: " JohnDoe NewYork Bottle", + expectedLeftSingle: []string{" Joh", "nDoe Ne", "wYork B", "ottle"}, + expectedLeftMulti: []string{" Joh", "nDoe Ne", "wYork B", "ottle"}, + expectedRightSingle: []string{" JohnDoe", " NewYork", " Bottle"}, + expectedRightMulti: []string{" JohnDoe", " NewYork", " Bottle"}, + }, + { + scenario: "right multi word data row", + referenceRow: " Name Last Seen Thing", + line: "JohnDoe two days ago Bottle", + expectedLeftSingle: []string{"Joh", "nDoe two", " days", " ago B", "ottle"}, + expectedLeftMulti: []string{"Joh", "nDoe two", " days ago B", "ottle"}, + expectedRightSingle: []string{"JohnDoe", " two day", "s ago", " Bottle"}, + expectedRightMulti: []string{"JohnDoe", " two days ago", " Bottle"}, + }, + { + scenario: "single column", + referenceRow: "Name", + line: "Blah Blah Blah", + expectedLeftSingle: []string{"Blah Blah Blah"}, + expectedLeftMulti: []string{"Blah Blah Blah"}, + expectedRightSingle: []string{"Blah Blah Blah"}, + expectedRightMulti: []string{"Blah Blah Blah"}, + }, + { + scenario: "empty row has one column", + referenceRow: "", + line: "anything", + expectedLeftSingle: []string{"anything"}, + expectedLeftMulti: []string{"anything"}, + expectedRightSingle: []string{"anything"}, + expectedRightMulti: []string{"anything"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.scenario, func(t *testing.T) { + { + indexes := parseLeftAlign(tc.referenceRow, false) + fields := split(tc.line, indexes) + assert.Equal(t, tc.expectedLeftSingle, fields) + assert.Equal(t, tc.line, strings.Join(fields, "")) + } + { + indexes := parseLeftAlign(tc.referenceRow, true) + fields := split(tc.line, indexes) + assert.Equal(t, tc.expectedLeftMulti, fields) + assert.Equal(t, tc.line, strings.Join(fields, "")) + } + { + indexes := parseRightAlign(tc.referenceRow, false) + fields := split(tc.line, indexes) + assert.Equal(t, tc.expectedRightSingle, fields) + assert.Equal(t, tc.line, strings.Join(fields, "")) + } + { + indexes := parseRightAlign(tc.referenceRow, true) + fields := split(tc.line, indexes) + assert.Equal(t, tc.expectedRightMulti, fields) + assert.Equal(t, tc.line, strings.Join(fields, "")) + } + }) + } +} + +func TestSplit(t *testing.T) { + + type testCase struct { + scenario string + indexes []int + line string + expected []string + } + + testCases := []testCase{ + { + scenario: "normal split", + indexes: []int{4, 8, 13}, + line: "abc123defghij", + expected: []string{"abc1", "23de", "fghij"}, + }, + { + scenario: "split with rest", + indexes: []int{4, 8}, + line: "abc123defghij", + expected: []string{"abc1", "23de", "fghij"}, + }, + { + scenario: "split shorter string", + indexes: []int{4, 8, 13}, + line: "abc123", + expected: []string{"abc1", "23"}, + }, + { + scenario: "empty indexes returns everything as one entry", + indexes: []int{}, + line: "abc123defghij", + expected: []string{"abc123defghij"}, + }, + { + scenario: "empty line with valid indexes returns nil", + indexes: []int{2, 4}, + line: "", + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.scenario, func(t *testing.T) { + result := split(tc.line, tc.indexes) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestWidthSplitter(t *testing.T) { + + type testCase struct { + scenario string + widthsStr string + line string + expected []string + expectedErr string + } + + testCases := []testCase{ + { + scenario: "empty widths spec", + widthsStr: "", + line: "blah blah", + expected: []string{"blah blah"}, + expectedErr: "", + }, + { + scenario: "simple", + widthsStr: "5,5,6", + line: "blah blah hello", + expected: []string{"blah ", "blah ", "hello"}, + expectedErr: "", + }, + { + scenario: "short line", + widthsStr: "5,5,6", + line: "blah blah hi", + expected: []string{"blah ", "blah ", "hi"}, + expectedErr: "", + }, + { + scenario: "long line", + widthsStr: "5,5,2", + line: "blah blah hello", + expected: []string{"blah ", "blah ", "he", "llo"}, + expectedErr: "", + }, + { + scenario: "non numeric width", + widthsStr: "a,b,c", + line: "blah blah", + expected: nil, + expectedErr: `invalid width: a, error: strconv.Atoi: parsing "a": invalid syntax`, + }, + { + scenario: "zero width", + widthsStr: "0,1,1", + line: "blah blah", + expected: nil, + expectedErr: `not a positive width: 0`, + }, + { + scenario: "negative width", + widthsStr: "1,-1,1", + line: "blah blah", + expected: nil, + expectedErr: `not a positive width: -1`, + }, + } + for _, tc := range testCases { + t.Run(tc.scenario, func(t *testing.T) { + indexes, err := parseWidths(tc.widthsStr) + if err != nil { + assert.Equal(t, tc.expectedErr, err.Error()) + } else { + fields := split(tc.line, indexes) + assert.Equal(t, tc.expected, fields) + assert.Equal(t, tc.line, strings.Join(fields, "")) + } + }) + } +} diff --git a/pkg/input/record_reader_pprint.go b/pkg/input/record_reader_pprint.go index 71867ddcb..402eca8b6 100644 --- a/pkg/input/record_reader_pprint.go +++ b/pkg/input/record_reader_pprint.go @@ -20,6 +20,10 @@ func NewRecordReaderPPRINT( if readerOptions.BarredPprintInput { // Implemented in this file + if readerOptions.FixedWidthSpec != "" { + return nil, fmt.Errorf("--fixed or --fw not allowed with barred input") + } + readerOptions.IFS = "|" readerOptions.AllowRepeatIFS = false @@ -36,6 +40,13 @@ func NewRecordReaderPPRINT( } return reader, nil + } else if readerOptions.FixedWidthSpec != "" { + reader := &RecordReaderPprintFixedSplit{ + readerOptions: readerOptions, + recordsPerBatch: recordsPerBatch, + separatorMatcher: regexp.MustCompile(`^[-=─ ]*$`), + } + return reader, nil } // Use the CSVLite record-reader, which is implemented in another file, // with multiple spaces instead of commas @@ -55,6 +66,234 @@ func NewRecordReaderPPRINT( return reader, nil } +type RecordReaderPprintFixedSplit struct { + readerOptions *cli.TReaderOptions + recordsPerBatch int64 + + separatorMatcher *regexp.Regexp + fieldSplitter *fixedWidthSplitter + + inputLineNumber int64 + headerStrings []string +} + +func (reader *RecordReaderPprintFixedSplit) Read( + filenames []string, + context types.Context, + readerChannel chan<- []*types.RecordAndContext, + errorChannel chan error, + downstreamDoneChannel <-chan bool, +) { + if filenames != nil { + if len(filenames) == 0 { + handle, err := lib.OpenStdin( + reader.readerOptions.Prepipe, + reader.readerOptions.PrepipeIsRaw, + reader.readerOptions.FileInputEncoding, + ) + if err != nil { + errorChannel <- err + } else { + reader.processHandle( + handle, + "(stdin)", + &context, + readerChannel, + errorChannel, + downstreamDoneChannel, + ) + } + } else { + for _, filename := range filenames { + handle, err := lib.OpenFileForRead( + filename, + reader.readerOptions.Prepipe, + reader.readerOptions.PrepipeIsRaw, + reader.readerOptions.FileInputEncoding, + ) + if err != nil { + errorChannel <- err + } else { + reader.processHandle( + handle, + filename, + &context, + readerChannel, + errorChannel, + downstreamDoneChannel, + ) + handle.Close() + } + } + } + } + readerChannel <- types.NewEndOfStreamMarkerList(&context) +} + +func (reader *RecordReaderPprintFixedSplit) processHandle( + handle io.Reader, + filename string, + context *types.Context, + readerChannel chan<- []*types.RecordAndContext, + errorChannel chan error, + downstreamDoneChannel <-chan bool, +) { + context.UpdateForStartOfFile(filename) + reader.inputLineNumber = 0 + reader.headerStrings = nil + + recordsPerBatch := reader.recordsPerBatch + lineReader := NewLineReader(handle, reader.readerOptions.IRS) + linesChannel := make(chan []string, recordsPerBatch) + go channelizedLineReader(lineReader, linesChannel, downstreamDoneChannel, recordsPerBatch) + + for { + recordsAndContexts, eof := reader.getRecords(linesChannel, filename, context, errorChannel) + if len(recordsAndContexts) > 0 { + readerChannel <- recordsAndContexts + } + if eof { + break + } + } +} + +func (reader *RecordReaderPprintFixedSplit) getRecords( + linesChannel <-chan []string, + filename string, + context *types.Context, + errorChannel chan error, +) (recordsAndContexts []*types.RecordAndContext, eof bool) { + recordsAndContexts = []*types.RecordAndContext{} + dedupeFieldNames := reader.readerOptions.DedupeFieldNames + + lines, more := <-linesChannel + if !more { + return recordsAndContexts, true + } + + for _, line := range lines { + reader.inputLineNumber++ + + if reader.readerOptions.CommentHandling != cli.CommentsAreData { + if strings.HasPrefix(line, reader.readerOptions.CommentString) { + if reader.readerOptions.CommentHandling == cli.PassComments { + recordsAndContexts = append(recordsAndContexts, types.NewOutputString(line+"\n", context)) + continue + } else if reader.readerOptions.CommentHandling == cli.SkipComments { + continue + } + } + } + + if line == "" { + reader.headerStrings = nil + reader.fieldSplitter = nil + continue + } + + // Skip lines like + // --------------------------------------- + // ======================================= + // ─────────────────────────────────────── + // ---- --------- ----- + if reader.separatorMatcher.MatchString(line) { + continue + } + + if reader.fieldSplitter == nil { + var err error + reader.fieldSplitter, err = NewFixedWidthSplitter(reader.readerOptions.FixedWidthSpec, line) + if err != nil { + errorChannel <- err + return + } + } + + fields := reader.fieldSplitter.Split(line) + for i, field := range fields { + fields[i] = strings.TrimSpace(field) + } + + if reader.headerStrings == nil { + if reader.readerOptions.UseImplicitHeader { + n := len(fields) + reader.headerStrings = make([]string, n) + for i := range n { + reader.headerStrings[i] = strconv.Itoa(i + 1) + } + } else { + reader.headerStrings = fields + continue + } + } else { + if !reader.readerOptions.AllowRaggedCSVInput && len(reader.headerStrings) != len(fields) { + err := fmt.Errorf( + "Fixed-width header/data length mismatch %d != %d at filename %s line %d", + len(reader.headerStrings), len(fields), filename, reader.inputLineNumber, + ) + errorChannel <- err + return + } + } + + record := mlrval.NewMlrmapAsRecord() + if !reader.readerOptions.AllowRaggedCSVInput { + for i, field := range fields { + value := mlrval.FromDeferredType(field) + _, err := record.PutReferenceMaybeDedupe(reader.headerStrings[i], value, dedupeFieldNames) + if err != nil { + errorChannel <- err + return + } + } + } else { + nh := int64(len(reader.headerStrings)) + nd := int64(len(fields)) + n := lib.IntMin2(nh, nd) + for i := int64(0); i < n; i++ { + field := fields[i] + value := mlrval.FromDeferredType(field) + _, err := record.PutReferenceMaybeDedupe(reader.headerStrings[i], value, dedupeFieldNames) + if err != nil { + errorChannel <- err + return + } + } + if nh < nd { + // if header shorter than data: use 1-up itoa keys + for i := nh; i < nd; i++ { + key := strconv.FormatInt(i+1, 10) + value := mlrval.FromDeferredType(fields[i]) + _, err := record.PutReferenceMaybeDedupe(key, value, dedupeFieldNames) + if err != nil { + errorChannel <- err + return + } + } + } + if nh > nd { + for i := nd; i < nh; i++ { + _, err := record.PutReferenceMaybeDedupe( + reader.headerStrings[i], + mlrval.VOID.Copy(), + dedupeFieldNames, + ) + if err != nil { + errorChannel <- err + return + } + } + } + } + + context.UpdateForInputRecord() + recordsAndContexts = append(recordsAndContexts, types.NewRecordAndContext(record, context)) + } + + return recordsAndContexts, false +} + type RecordReaderPprintBarredOrMarkdown struct { readerOptions *cli.TReaderOptions recordsPerBatch int64 // distinct from readerOptions.RecordsPerBatch for join/repl diff --git a/test/cases/cli-fixed-width/0001/cmd b/test/cases/cli-fixed-width/0001/cmd new file mode 100644 index 000000000..ceffa48a4 --- /dev/null +++ b/test/cases/cli-fixed-width/0001/cmd @@ -0,0 +1 @@ +mlr --p2j --fw cat test/cases/cli-fixed-width/0001/input diff --git a/test/cases/cli-fixed-width/0001/experr b/test/cases/cli-fixed-width/0001/experr new file mode 100644 index 000000000..e69de29bb diff --git a/test/cases/cli-fixed-width/0001/expout b/test/cases/cli-fixed-width/0001/expout new file mode 100644 index 000000000..05f9d40d2 --- /dev/null +++ b/test/cases/cli-fixed-width/0001/expout @@ -0,0 +1,17 @@ +[ +{ + "Name": "JohnDoe", + "Last Seen": "two days ago", + "Thing": "Bottle" +}, +{ + "Name": "FooBar12", + "Last Seen": "online", + "Thing": "Pen" +}, +{ + "Name": "Max", + "Last Seen": "yesterday", + "Thing": "cup" +} +] diff --git a/test/cases/cli-fixed-width/0001/input b/test/cases/cli-fixed-width/0001/input new file mode 100644 index 000000000..c57421aea --- /dev/null +++ b/test/cases/cli-fixed-width/0001/input @@ -0,0 +1,4 @@ +Name Last Seen Thing +JohnDoe two days ago Bottle +FooBar12 online Pen +Max yesterday cup diff --git a/test/cases/cli-fixed-width/0002/cmd b/test/cases/cli-fixed-width/0002/cmd new file mode 100644 index 000000000..fe0600926 --- /dev/null +++ b/test/cases/cli-fixed-width/0002/cmd @@ -0,0 +1 @@ +mlr --p2j --fixed right-align cat test/cases/cli-fixed-width/0002/input diff --git a/test/cases/cli-fixed-width/0002/experr b/test/cases/cli-fixed-width/0002/experr new file mode 100644 index 000000000..e69de29bb diff --git a/test/cases/cli-fixed-width/0002/expout b/test/cases/cli-fixed-width/0002/expout new file mode 100644 index 000000000..75641f5ad --- /dev/null +++ b/test/cases/cli-fixed-width/0002/expout @@ -0,0 +1,17 @@ +[ +{ + "Name": "JohnDoe", + "Place": "New York", + "Thing": "Bottle" +}, +{ + "Name": "FooBar12", + "Place": "Seattle", + "Thing": "Pen" +}, +{ + "Name": "Max", + "Place": "London", + "Thing": "cup" +} +] diff --git a/test/cases/cli-fixed-width/0002/input b/test/cases/cli-fixed-width/0002/input new file mode 100644 index 000000000..ad40c1e35 --- /dev/null +++ b/test/cases/cli-fixed-width/0002/input @@ -0,0 +1,4 @@ + Name Place Thing + JohnDoe New York Bottle +FooBar12 Seattle Pen + Max London cup diff --git a/test/cases/cli-fixed-width/0003/cmd b/test/cases/cli-fixed-width/0003/cmd new file mode 100644 index 000000000..e5bf50027 --- /dev/null +++ b/test/cases/cli-fixed-width/0003/cmd @@ -0,0 +1 @@ +mlr --p2j --fixed widths:10,14 cat test/cases/cli-fixed-width/0003/input diff --git a/test/cases/cli-fixed-width/0003/experr b/test/cases/cli-fixed-width/0003/experr new file mode 100644 index 000000000..e69de29bb diff --git a/test/cases/cli-fixed-width/0003/expout b/test/cases/cli-fixed-width/0003/expout new file mode 100644 index 000000000..05f9d40d2 --- /dev/null +++ b/test/cases/cli-fixed-width/0003/expout @@ -0,0 +1,17 @@ +[ +{ + "Name": "JohnDoe", + "Last Seen": "two days ago", + "Thing": "Bottle" +}, +{ + "Name": "FooBar12", + "Last Seen": "online", + "Thing": "Pen" +}, +{ + "Name": "Max", + "Last Seen": "yesterday", + "Thing": "cup" +} +] diff --git a/test/cases/cli-fixed-width/0003/input b/test/cases/cli-fixed-width/0003/input new file mode 100644 index 000000000..9ffcd63ce --- /dev/null +++ b/test/cases/cli-fixed-width/0003/input @@ -0,0 +1,7 @@ +============================== +Name Last Seen Thing +---- --------- ------ +JohnDoe two days ago Bottle +FooBar12 online Pen +Max yesterday cup +============================== diff --git a/test/cases/cli-fixed-width/0004/cmd b/test/cases/cli-fixed-width/0004/cmd new file mode 100644 index 000000000..c0ed4ca48 --- /dev/null +++ b/test/cases/cli-fixed-width/0004/cmd @@ -0,0 +1 @@ +mlr --p2p --skip-comments --p2j --ragged --fw cat test/cases/cli-fixed-width/0004/input diff --git a/test/cases/cli-fixed-width/0004/experr b/test/cases/cli-fixed-width/0004/experr new file mode 100644 index 000000000..e69de29bb diff --git a/test/cases/cli-fixed-width/0004/expout b/test/cases/cli-fixed-width/0004/expout new file mode 100644 index 000000000..1268d02cf --- /dev/null +++ b/test/cases/cli-fixed-width/0004/expout @@ -0,0 +1,21 @@ +[ +{ + "Name": "JohnDoe", + "Last Seen": "two days ago", + "Thing": "Bottle" +}, +{ + "Name": "FooBar12", + "Last Seen": "online", + "Thing": "Pen" +}, +{ + "Name": "Max", + "Last Seen": "yesterday", + "Thing": "" +}, +{ + "Name": "Doe", + "Thing": "Bottle" +} +] diff --git a/test/cases/cli-fixed-width/0004/input b/test/cases/cli-fixed-width/0004/input new file mode 100644 index 000000000..ada31563f --- /dev/null +++ b/test/cases/cli-fixed-width/0004/input @@ -0,0 +1,11 @@ +# This is a comment +Name Last Seen Thing +---- --------- ----- +JohnDoe two days ago Bottle +FooBar12 online Pen +Max yesterday + +# New schema followed by empty line +Name Thing +---- ----- +Doe Bottle diff --git a/test/cases/cli-fixed-width/0005/cmd b/test/cases/cli-fixed-width/0005/cmd new file mode 100644 index 000000000..c2ec1e7c0 --- /dev/null +++ b/test/cases/cli-fixed-width/0005/cmd @@ -0,0 +1 @@ +mlr -N --p2j --ragged --fixed widths:11,2,6,13 cat test/cases/cli-fixed-width/0005/input diff --git a/test/cases/cli-fixed-width/0005/experr b/test/cases/cli-fixed-width/0005/experr new file mode 100644 index 000000000..e69de29bb diff --git a/test/cases/cli-fixed-width/0005/expout b/test/cases/cli-fixed-width/0005/expout new file mode 100644 index 000000000..77a538f3b --- /dev/null +++ b/test/cases/cli-fixed-width/0005/expout @@ -0,0 +1,68 @@ +[ +{ + "1": "total 28" +}, +{ + "1": "-rw-r--r--", + "2": 1, + "3": 28, + "4": "Jan 9 14:43", + "5": "_config.yml" +}, +{ + "1": "-rw-r--r--", + "2": 1, + "3": 141, + "4": "Jan 9 14:43", + "5": "Makefile" +}, +{ + "1": "-rw-r--r--", + "2": 1, + "3": 5701, + "4": "Feb 23 17:02", + "5": "mkdocs.yml" +}, +{ + "1": "drwxr-xr-x", + "2": 1, + "3": 16, + "4": "Jan 9 14:43", + "5": "overrides" +}, +{ + "1": "-rwxr-xr-x", + "2": 1, + "3": 69, + "4": "Jan 9 14:43", + "5": "regen.sh" +}, +{ + "1": "-rw-r--r--", + "2": 1, + "3": 16, + "4": "Jan 9 14:43", + "5": "requirements.txt" +}, +{ + "1": "-rwxr-xr-x", + "2": 1, + "3": 900, + "4": "Jan 9 14:43", + "5": "retitle" +}, +{ + "1": "drwxr-xr-x", + "2": 1, + "3": 9422, + "4": "Feb 22 17:15", + "5": "site" +}, +{ + "1": "drwxr-xr-x", + "2": 1, + "3": 10018, + "4": "Feb 23 17:02", + "5": "src" +} +] diff --git a/test/cases/cli-fixed-width/0005/input b/test/cases/cli-fixed-width/0005/input new file mode 100644 index 000000000..fe631f313 --- /dev/null +++ b/test/cases/cli-fixed-width/0005/input @@ -0,0 +1,10 @@ +total 28 +-rw-r--r-- 1 28 Jan 9 14:43 _config.yml +-rw-r--r-- 1 141 Jan 9 14:43 Makefile +-rw-r--r-- 1 5701 Feb 23 17:02 mkdocs.yml +drwxr-xr-x 1 16 Jan 9 14:43 overrides +-rwxr-xr-x 1 69 Jan 9 14:43 regen.sh +-rw-r--r-- 1 16 Jan 9 14:43 requirements.txt +-rwxr-xr-x 1 900 Jan 9 14:43 retitle +drwxr-xr-x 1 9422 Feb 22 17:15 site +drwxr-xr-x 1 10018 Feb 23 17:02 src