diff --git a/go/regtest/cases/cli-help/0001/expout b/go/regtest/cases/cli-help/0001/expout index 169f6356d..1ebb60c47 100644 --- a/go/regtest/cases/cli-help/0001/expout +++ b/go/regtest/cases/cli-help/0001/expout @@ -729,6 +729,7 @@ which is the same as: Usage: mlr sort-within-records [options] Outputs records sorted lexically ascending by keys. Options: +-r Recursively sort subobjects/submaps, e.g. for JSON input. -h|--help Show this message. ================================================================ Usage: mlr stats1 [options] diff --git a/go/regtest/cases/verb-sort-within-records/0001/cmd b/go/regtest/cases/verb-sort-within-records/0001/cmd index 6f7757950..cb6236ae9 100644 --- a/go/regtest/cases/verb-sort-within-records/0001/cmd +++ b/go/regtest/cases/verb-sort-within-records/0001/cmd @@ -1 +1 @@ -mlr --from regtest/input/sort-within-records.dkvp sort-within-records +mlr --from regtest/input/sort-within-records.dkvp sort-within-records diff --git a/go/regtest/cases/verb-sort-within-records/0002/cmd b/go/regtest/cases/verb-sort-within-records/0002/cmd index 48b5ea6d0..0e92b9ded 100644 --- a/go/regtest/cases/verb-sort-within-records/0002/cmd +++ b/go/regtest/cases/verb-sort-within-records/0002/cmd @@ -1 +1 @@ -mlr --json --from regtest/input/needs-sorting.json sort-within-records +mlr --json --from regtest/input/needs-sorting.json sort-within-records diff --git a/go/regtest/cases/verb-sort-within-records/0003/cmd b/go/regtest/cases/verb-sort-within-records/0003/cmd new file mode 100644 index 000000000..a26bbd59f --- /dev/null +++ b/go/regtest/cases/verb-sort-within-records/0003/cmd @@ -0,0 +1 @@ +mlr --json --from regtest/input/needs-sorting.json sort-within-records -r diff --git a/go/regtest/cases/verb-sort-within-records/0003/experr b/go/regtest/cases/verb-sort-within-records/0003/experr new file mode 100644 index 000000000..e69de29bb diff --git a/go/regtest/cases/verb-sort-within-records/0003/expout b/go/regtest/cases/verb-sort-within-records/0003/expout new file mode 100644 index 000000000..7a56a44ef --- /dev/null +++ b/go/regtest/cases/verb-sort-within-records/0003/expout @@ -0,0 +1,13 @@ +{ + "a": 1, + "b": 2 +} +{ + "a": 1, + "b": 2 +} +{ + "a": 1, + "b": 2, + "c": 3 +} diff --git a/go/src/transformers/sort-within-records.go b/go/src/transformers/sort-within-records.go index e557bf42a..5c96c8f90 100644 --- a/go/src/transformers/sort-within-records.go +++ b/go/src/transformers/sort-within-records.go @@ -29,6 +29,7 @@ func transformerSortWithinRecordsUsage( fmt.Fprintf(o, "Usage: %s %s [options]\n", lib.MlrExeName(), verbNameSortWithinRecords) fmt.Fprintln(o, "Outputs records sorted lexically ascending by keys.") fmt.Fprintf(o, "Options:\n") + fmt.Fprintf(o, "-r Recursively sort subobjects/submaps, e.g. for JSON input.\n") fmt.Fprintf(o, "-h|--help Show this message.\n") if doExit { @@ -47,6 +48,7 @@ func transformerSortWithinRecordsParseCLI( // Skip the verb name from the current spot in the mlr command line argi := *pargi argi++ + doRecurse := false for argi < argc /* variable increment: 1 or 2 depending on flag */ { opt := args[argi] @@ -58,6 +60,9 @@ func transformerSortWithinRecordsParseCLI( if opt == "-h" || opt == "--help" { transformerSortWithinRecordsUsage(os.Stdout, true, 0) + } else if opt == "-r" { + doRecurse = true + } else { transformerSortWithinRecordsUsage(os.Stderr, true, 1) } @@ -66,7 +71,7 @@ func transformerSortWithinRecordsParseCLI( // TODO: allow sort by key or value? // TODO: allow sort ascendending/descending? - transformer, err := NewTransformerSortWithinRecords() + transformer, err := NewTransformerSortWithinRecords(doRecurse) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -78,11 +83,19 @@ func transformerSortWithinRecordsParseCLI( // ---------------------------------------------------------------- type TransformerSortWithinRecords struct { + recordTransformerFunc transforming.RecordTransformerFunc } -func NewTransformerSortWithinRecords() (*TransformerSortWithinRecords, error) { +func NewTransformerSortWithinRecords( + doRecurse bool, +) (*TransformerSortWithinRecords, error) { this := &TransformerSortWithinRecords{} + if doRecurse { + this.recordTransformerFunc = this.transformRecursively + } else { + this.recordTransformerFunc = this.transformNonrecursively + } return this, nil } @@ -91,6 +104,14 @@ func NewTransformerSortWithinRecords() (*TransformerSortWithinRecords, error) { func (this *TransformerSortWithinRecords) Transform( inrecAndContext *types.RecordAndContext, outputChannel chan<- *types.RecordAndContext, +) { + this.recordTransformerFunc(inrecAndContext, outputChannel) +} + +// ---------------------------------------------------------------- +func (this *TransformerSortWithinRecords) transformNonrecursively( + inrecAndContext *types.RecordAndContext, + outputChannel chan<- *types.RecordAndContext, ) { if !inrecAndContext.EndOfStream { inrec := inrecAndContext.Record @@ -98,3 +119,15 @@ func (this *TransformerSortWithinRecords) Transform( } outputChannel <- inrecAndContext // including end-of-stream marker } + +// ---------------------------------------------------------------- +func (this *TransformerSortWithinRecords) transformRecursively( + inrecAndContext *types.RecordAndContext, + outputChannel chan<- *types.RecordAndContext, +) { + if !inrecAndContext.EndOfStream { + inrec := inrecAndContext.Record + inrec.SortByKeyRecursively() + } + outputChannel <- inrecAndContext // including end-of-stream marker +} diff --git a/go/src/types/mlrmap_accessors.go b/go/src/types/mlrmap_accessors.go index 4b5b94515..45a83311e 100644 --- a/go/src/types/mlrmap_accessors.go +++ b/go/src/types/mlrmap_accessors.go @@ -688,6 +688,26 @@ func (this *Mlrmap) SortByKey() { *this = *that } +// ---------------------------------------------------------------- +func (this *Mlrmap) SortByKeyRecursively() { + keys := this.GetKeys() + + lib.SortStrings(keys) + + that := NewMlrmapAsRecord() + + for _, key := range keys { + // Old record will be GC'ed: just move pointers + value := this.Get(key) + if value.IsMap() { + value.mapval.SortByKeyRecursively() + } + that.PutReference(key, value) + } + + *this = *that +} + // ================================================================ // PRIVATE METHODS