1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
package transformers
import (
"container/list"
"fmt"
"os"
"strings"
"github.com/johnkerl/miller/v6/pkg/cli"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/types"
)
// ----------------------------------------------------------------
const verbNameFormatValues = "format-values"
const defaultFormatValuesStringFormat = "%s"
const defaultFormatValuesIntFormat = "%d"
const defaultFormatValuesFloatFormat = "%f"
var FormatValuesSetup = TransformerSetup{
Verb: verbNameFormatValues,
UsageFunc: transformerFormatValuesUsage,
ParseCLIFunc: transformerFormatValuesParseCLI,
IgnoresInput: false,
}
// ----------------------------------------------------------------
func transformerFormatValuesUsage(
o *os.File,
) {
fmt.Fprintf(o, "Usage: %s %s [options]\n", "mlr", verbNameFormatValues)
fmt.Fprintf(o, "Applies format strings to all field values, depending on autodetected type.\n")
fmt.Fprintf(o, "* If a field value is detected to be integer, applies integer format.\n")
fmt.Fprintf(o, "* Else, if a field value is detected to be float, applies float format.\n")
fmt.Fprintf(o, "* Else, applies string format.\n")
fmt.Fprintf(o, "\n")
fmt.Fprintf(o, "Note: this is a low-keystroke way to apply formatting to many fields. To get\n")
fmt.Fprintf(o, "finer control, please see the fmtnum function within the mlr put DSL.\n")
fmt.Fprintf(o, "\n")
fmt.Fprintf(o, "Note: this verb lets you apply arbitrary format strings, which can produce\n")
fmt.Fprintf(o, "undefined behavior and/or program crashes. See your system's \"man printf\".\n")
fmt.Fprintf(o, "\n")
fmt.Fprintf(o, "Options:\n")
fmt.Fprintf(o, "-i {integer format} Defaults to \"%s\".\n", defaultFormatValuesIntFormat)
fmt.Fprintf(o, " Examples: \"%%06lld\", \"%%08llx\".\n")
fmt.Fprintf(o, " Note that Miller integers are long long so you must use\n")
fmt.Fprintf(o, " formats which apply to long long, e.g. with ll in them.\n")
fmt.Fprintf(o, " Undefined behavior results otherwise.\n")
fmt.Fprintf(o, "-f {float format} Defaults to \"%s\".\n", defaultFormatValuesFloatFormat)
fmt.Fprintf(o, " Examples: \"%%8.3lf\", \"%%.6le\".\n")
fmt.Fprintf(o, " Note that Miller floats are double-precision so you must\n")
fmt.Fprintf(o, " use formats which apply to double, e.g. with l[efg] in them.\n")
fmt.Fprintf(o, " Undefined behavior results otherwise.\n")
fmt.Fprintf(o, "-s {string format} Defaults to \"%s\".\n", defaultFormatValuesStringFormat)
fmt.Fprintf(o, " Examples: \"_%%s\", \"%%08s\".\n")
fmt.Fprintf(o, " Note that you must use formats which apply to string, e.g.\n")
fmt.Fprintf(o, " with s in them. Undefined behavior results otherwise.\n")
fmt.Fprintf(o, "-n Coerce field values autodetected as int to float, and then\n")
fmt.Fprintf(o, " apply the float format.\n")
}
func transformerFormatValuesParseCLI(
pargi *int,
argc int,
args []string,
_ *cli.TOptions,
doConstruct bool, // false for first pass of CLI-parse, true for second pass
) IRecordTransformer {
// Skip the verb name from the current spot in the mlr command line
argi := *pargi
verb := args[argi]
argi++
stringFormat := defaultFormatValuesStringFormat
intFormat := defaultFormatValuesIntFormat
floatFormat := defaultFormatValuesFloatFormat
coerceIntToFloat := false
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++
if opt == "-h" || opt == "--help" {
transformerFormatValuesUsage(os.Stdout)
os.Exit(0)
} else if opt == "-s" {
stringFormat = cli.VerbGetStringArgOrDie(verb, opt, args, &argi, argc)
} else if opt == "-i" {
intFormat = cli.VerbGetStringArgOrDie(verb, opt, args, &argi, argc)
} else if opt == "-f" {
floatFormat = cli.VerbGetStringArgOrDie(verb, opt, args, &argi, argc)
} else if opt == "-n" {
coerceIntToFloat = true
} else {
transformerFormatValuesUsage(os.Stderr)
os.Exit(1)
}
}
*pargi = argi
if !doConstruct { // All transformers must do this for main command-line parsing
return nil
}
transformer, err := NewTransformerFormatValues(
stringFormat,
intFormat,
floatFormat,
coerceIntToFloat,
)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
return transformer
}
// ----------------------------------------------------------------
type TransformerFormatValues struct {
stringFormatter mlrval.IFormatter
intFormatter mlrval.IFormatter
floatFormatter mlrval.IFormatter
coerceIntToFloat bool
}
func NewTransformerFormatValues(
stringFormat string,
intFormat string,
floatFormat string,
coerceIntToFloat bool,
) (*TransformerFormatValues, error) {
stringFormatter, err := mlrval.GetFormatter(stringFormat)
if err != nil {
return nil, err
}
intFormatter, err := mlrval.GetFormatter(intFormat)
if err != nil {
return nil, err
}
floatFormatter, err := mlrval.GetFormatter(floatFormat)
if err != nil {
return nil, err
}
tr := &TransformerFormatValues{
stringFormatter: stringFormatter,
intFormatter: intFormatter,
floatFormatter: floatFormatter,
coerceIntToFloat: coerceIntToFloat,
}
return tr, nil
}
// ----------------------------------------------------------------
func (tr *TransformerFormatValues) Transform(
inrecAndContext *types.RecordAndContext,
outputRecordsAndContexts *list.List, // list of *types.RecordAndContext
inputDownstreamDoneChannel <-chan bool,
outputDownstreamDoneChannel chan<- bool,
) {
HandleDefaultDownstreamDone(inputDownstreamDoneChannel, outputDownstreamDoneChannel)
if inrecAndContext.EndOfStream {
outputRecordsAndContexts.PushBack(inrecAndContext) // emit end-of-stream marker
return
}
for pe := inrecAndContext.Record.Head; pe != nil; pe = pe.Next {
if tr.coerceIntToFloat {
_, isNumeric := pe.Value.GetNumericToFloatValue()
if isNumeric {
pe.Value = tr.floatFormatter.Format(pe.Value)
} else if pe.Value.IsStringOrVoid() {
pe.Value = tr.stringFormatter.Format(pe.Value)
} // else, don't rewrite booleans, arrays, maps, etc.
} else {
_, isInt := pe.Value.GetIntValue()
_, isFloat := pe.Value.GetFloatValue()
if isInt {
pe.Value = tr.intFormatter.Format(pe.Value)
} else if isFloat {
pe.Value = tr.floatFormatter.Format(pe.Value)
} else if pe.Value.IsStringOrVoid() {
pe.Value = tr.stringFormatter.Format(pe.Value)
} // else, don't rewrite booleans, arrays, maps, etc.
}
}
outputRecordsAndContexts.PushBack(inrecAndContext)
}
|