diff --git a/main.go b/main.go index 564b4c1..92db1d6 100644 --- a/main.go +++ b/main.go @@ -18,8 +18,21 @@ var ( // format string with {0} as placeholders // {0} always matches the whole line // {1} and onwards match their respective sub groups + // // You can optionally specify a printf-syntax for formatting like this: {1:%s} or {1:%02d} - // printf-syntax is currently supported for: strings, floats, integers + // printf-syntax is currently supported for: strings, floats, integers. + // + // Addtionally mutators can be provided + // to further manipulate the value using the given syntax: {1:%d:+1} + // + // The value mutated by can also be a back reference to another group + // using round brackets like this: {1:%d:+(2)}} + // + // Multiple mutators can be used at once: {1:%d:+2*3-(2)} + // Be aware that they will be applied strictly from left to right! + // + // The following number mutators (integers and floats) are allowed: + // + - * / ^ % output = flag.String("o", "{0}", "output pattern") // don't ignore lines which do not match against input. @@ -31,7 +44,9 @@ var ( // it may be useful to have a boolean flag for this behavior lineParseAmount = flag.Int("n", 1, "amount of lines to feed into input pattern") - replacePattern = regexp.MustCompile(`\{(\d+)(?::(.*?))?\}`) + replacePattern = regexp.MustCompile(`\{(\d+)(?::(.*?))?(?::(.*?))?\}`) + + numMutationPattern = regexp.MustCompile(`([+\-*/])(\d+|\((\d+)\))`) ) func main() { @@ -100,10 +115,12 @@ func replaceVars(format string, vars ...string) string { if strings.HasSuffix(rplFmt, "d") { // replace integers value, _ := strconv.ParseInt(vars[varIndex], 10, 64) - format = strings.Replace(format, rplStr, fmt.Sprintf(rplFmt, value), 1) + mutate := numMut2func[int64](replacement[3]) + format = strings.Replace(format, rplStr, fmt.Sprintf(rplFmt, mutate(value, vars)), 1) } else if strings.HasSuffix(rplFmt, "f") { // replace floats value, _ := strconv.ParseFloat(vars[varIndex], 64) - format = strings.Replace(format, rplStr, fmt.Sprintf(rplFmt, value), 1) + mutate := numMut2func[float64](replacement[3]) + format = strings.Replace(format, rplStr, fmt.Sprintf(rplFmt, mutate(value, vars)), 1) } else { // replace strings format = strings.Replace(format, rplStr, fmt.Sprintf(rplFmt, vars[varIndex]), 1) } @@ -112,3 +129,67 @@ func replaceVars(format string, vars ...string) string { return format } + +var numMutatorCache = map[string]interface{}{} + +func numMut2func[T int64 | float64](mutation string) (f func(value T, vars []string) T) { + if mutation == "" { + return func(value T, vars []string) T { return value } + } + + // caching + if v, ok := numMutatorCache[mutation]; ok { + return v.(func(value T, vars []string) T) + } + defer func() { numMutatorCache[mutation] = f }() + + matches := numMutationPattern.FindAllStringSubmatch(mutation, -1) + mutators := make([]NumMutator, 0, len(matches)) + var err error + for _, match := range matches { + mut := NumMutator{Op: NewNumOperatorFromString(match[1])} + if match[3] == "" { + mut.Value, err = strconv.Atoi(match[2]) + mut.Var = false + if err != nil { + panic("invalid number in number mutator: " + match[2]) + } + } else { + mut.Var = true + mut.Value, err = strconv.Atoi(match[3]) + if err != nil { + panic("invalid back reference group in number mutator: " + match[2]) + } + } + mutators = append(mutators, mut) + } + + return func(value T, vars []string) T { + for _, mutator := range mutators { + var otherValue T + if mutator.Var { + other, err := strconv.Atoi(vars[mutator.Value]) + if err != nil { + panic("back reference group for number mutator is not a number") + } + otherValue = T(other) + } else { + otherValue = T(mutator.Value) + } + + switch mutator.Op { + case NumOperatorAdd: + value += otherValue + case NumOperatorSub: + value -= otherValue + case NumOperatorMul: + value *= otherValue + case NumOperatorDiv: + value /= otherValue + default: + } + } + + return value + } +} diff --git a/mutator.go b/mutator.go new file mode 100644 index 0000000..ad68b15 --- /dev/null +++ b/mutator.go @@ -0,0 +1,31 @@ +package main + +type NumMutator struct { + Op NumOperator + Var bool + Value int +} + +type NumOperator string + +const ( + NumOperatorAdd NumOperator = "+" + NumOperatorSub NumOperator = "-" + NumOperatorMul NumOperator = "*" + NumOperatorDiv NumOperator = "/" +) + +func NewNumOperatorFromString(str string) NumOperator { + switch str { + case "+": + return NumOperatorAdd + case "-": + return NumOperatorSub + case "*": + return NumOperatorMul + case "/": + return NumOperatorDiv + default: + panic("invalid number operator: " + str) + } +}