package transformers import ( "fmt" "os" "strings" "golang.org/x/text/cases" "golang.org/x/text/language" "github.com/johnkerl/miller/v6/pkg/cli" "github.com/johnkerl/miller/v6/pkg/lib" "github.com/johnkerl/miller/v6/pkg/mlrval" "github.com/johnkerl/miller/v6/pkg/types" ) const verbNameCase = "case" var caseOptions = []OptionSpec{ {Flag: "-k", Type: "bool", Desc: "Case only keys, not keys and values."}, {Flag: "-v", Type: "bool", Desc: "Case only values, not keys and values."}, {Flag: "-f", Arg: "{a,b,c}", Type: "csv-list", Desc: "Specify which field names to case (default: all)."}, {Flag: "-u", Type: "bool", Desc: "Convert to uppercase."}, {Flag: "-l", Type: "bool", Desc: "Convert to lowercase."}, {Flag: "-s", Type: "bool", Desc: "Convert to sentence case (capitalize first letter)."}, {Flag: "-t", Type: "bool", Desc: "Convert to title case (capitalize words)."}, } var CaseSetup = TransformerSetup{ Verb: verbNameCase, UsageFunc: transformerCaseUsage, ParseCLIFunc: transformerCaseParseCLI, IgnoresInput: false, Options: caseOptions, } const ( e_UNSPECIFIED_CASE = iota e_UPPER_CASE e_LOWER_CASE e_SENTENCE_CASE e_TITLE_CASE ) func transformerCaseUsage( o *os.File, ) { fmt.Fprintf(o, "Usage: %s %s [options]\n", "mlr", verbNameCase) fmt.Fprintf(o, "Uppercases strings in record keys and/or values.\n") WriteVerbOptions(o, caseOptions) } func transformerCaseParseCLI( pargi *int, argc int, args []string, _ *cli.TOptions, doConstruct bool, // false for first pass of CLI-parse, true for second pass ) (RecordTransformer, error) { // Skip the verb name from the current spot in the mlr command line argi := *pargi verb := args[argi] argi++ var err error which := "keys_and_values" style := e_UNSPECIFIED_CASE var fieldNames []string = nil for argi < argc /* variable increment: 1 or 2 depending on flag */ { opt := args[argi] if !strings.HasPrefix(opt, "-") { break // No more flag options to process } if args[argi] == "--" { break // All transformers must do this so main-flags can follow verb-flags } argi++ switch opt { case "-h", "--help": transformerCaseUsage(os.Stdout) return nil, cli.ErrHelpRequested case "-k": which = "keys_only" case "-v": which = "values_only" case "-f": fieldNames, err = cli.VerbGetStringArrayArg(verb, opt, args, &argi, argc) if err != nil { return nil, err } case "-u": style = e_UPPER_CASE case "-l": style = e_LOWER_CASE case "-s": style = e_SENTENCE_CASE case "-t": style = e_TITLE_CASE default: return nil, cli.VerbErrorf(verb, "option \"%s\" not recognized", opt) } } *pargi = argi if !doConstruct { // All transformers must do this for main command-line parsing return nil, nil } transformer, err := NewTransformerCase(which, fieldNames, style) if err != nil { return nil, err } return transformer, nil } type caserFuncT func(input string) string type TransformerCase struct { recordTransformerFunc RecordTransformerFunc fieldNameSet map[string]bool caserFunc caserFuncT } func NewTransformerCase( which string, fieldNames []string, style int, ) (*TransformerCase, error) { tr := &TransformerCase{} switch which { case "keys_only": tr.recordTransformerFunc = tr.transformKeysOnly case "values_only": tr.recordTransformerFunc = tr.transformValuesOnly default: tr.recordTransformerFunc = tr.transformKeysAndValues } if fieldNames != nil { tr.fieldNameSet = lib.StringListToSet(fieldNames) } switch style { case e_UPPER_CASE: tr.caserFunc = cases.Upper(language.Und).String case e_LOWER_CASE: tr.caserFunc = cases.Lower(language.Und).String case e_SENTENCE_CASE: tr.caserFunc = caseSentenceFunc case e_TITLE_CASE: tr.caserFunc = cases.Title(language.Und).String default: return nil, fmt.Errorf( "mlr %s: case option must be specified using one of -u, -l, -s, -t", verbNameCase, ) } return tr, nil } func caseSentenceFunc(input string) string { runes := []rune(input) if len(runes) == 0 { return input } first := string(runes[0]) rest := string(runes[1:]) return strings.ToUpper(first) + strings.ToLower(rest) } func (tr *TransformerCase) Transform( inrecAndContext *types.RecordAndContext, outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext inputDownstreamDoneChannel <-chan bool, outputDownstreamDoneChannel chan<- bool, ) { HandleDefaultDownstreamDone(inputDownstreamDoneChannel, outputDownstreamDoneChannel) if !inrecAndContext.EndOfStream { tr.recordTransformerFunc( inrecAndContext, outputRecordsAndContexts, inputDownstreamDoneChannel, outputDownstreamDoneChannel, ) } else { // end of record stream *outputRecordsAndContexts = append(*outputRecordsAndContexts, inrecAndContext) } } func (tr *TransformerCase) transformKeysOnly( inrecAndContext *types.RecordAndContext, outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext _ <-chan bool, __ chan<- bool, ) { inrec := inrecAndContext.Record newrec := mlrval.NewMlrmapAsRecord() for pe := inrec.Head; pe != nil; pe = pe.Next { if tr.fieldNameSet == nil || tr.fieldNameSet[pe.Key] { newkey := tr.caserFunc(pe.Key) // Reference not copy since this is ownership transfer of the value from the now-abandoned inrec newrec.PutReference(newkey, pe.Value) } else { newrec.PutReference(pe.Key, pe.Value) } } *outputRecordsAndContexts = append(*outputRecordsAndContexts, types.NewRecordAndContext(newrec, &inrecAndContext.Context)) } func (tr *TransformerCase) transformValuesOnly( inrecAndContext *types.RecordAndContext, outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext _ <-chan bool, __ chan<- bool, ) { inrec := inrecAndContext.Record for pe := inrec.Head; pe != nil; pe = pe.Next { if tr.fieldNameSet == nil || tr.fieldNameSet[pe.Key] { stringval, ok := pe.Value.GetStringValue() if ok { pe.Value = mlrval.FromString(tr.caserFunc(stringval)) } } } *outputRecordsAndContexts = append(*outputRecordsAndContexts, types.NewRecordAndContext(inrec, &inrecAndContext.Context)) } func (tr *TransformerCase) transformKeysAndValues( inrecAndContext *types.RecordAndContext, outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext _ <-chan bool, __ chan<- bool, ) { inrec := inrecAndContext.Record newrec := mlrval.NewMlrmapAsRecord() for pe := inrec.Head; pe != nil; pe = pe.Next { if tr.fieldNameSet == nil || tr.fieldNameSet[pe.Key] { newkey := tr.caserFunc(pe.Key) stringval, ok := pe.Value.GetStringValue() if ok { stringval = tr.caserFunc(stringval) newrec.PutReference(newkey, mlrval.FromString(stringval)) } else { newrec.PutReference(newkey, pe.Value) } } else { newrec.PutReference(pe.Key, pe.Value) } } *outputRecordsAndContexts = append(*outputRecordsAndContexts, types.NewRecordAndContext(newrec, &inrecAndContext.Context)) }