diff --git a/color.go b/color.go new file mode 100644 index 0000000..da41997 --- /dev/null +++ b/color.go @@ -0,0 +1,36 @@ +package main + +import "github.com/fatih/color" + +var colorCache = map[string]*color.Color{} + +func makeColor(name string) (c *color.Color) { + // caching + if c, ok := colorCache[name]; ok { + return c + } + defer func() { colorCache[name] = c }() + + switch name { + case "black": + return color.New(color.FgBlack) + case "red": + return color.New(color.FgRed) + case "green": + return color.New(color.FgGreen) + case "yellow": + return color.New(color.FgYellow) + case "blue": + return color.New(color.FgBlue) + case "magenta": + return color.New(color.FgMagenta) + case "cyan": + return color.New(color.FgCyan) + case "white": + return color.New(color.FgWhite) + case "": + return color.New() + default: + panic("unknown color name. valid color names: black, red, green, yellow, blue, magenta, cyan, white") + } +} diff --git a/go.mod b/go.mod index 11d3c6d..65fdcc5 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module git.tordarus.net/Tordarus/format go 1.18 + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c970384 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e h1:w36l2Uw3dRan1K3TyXriXvY+6T56GNmlKGcqiQUJDfM= +golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index e04db74..f7aac01 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "os" - "reflect" "regexp" "strconv" "strings" @@ -46,7 +45,7 @@ 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+)\))`) ) @@ -123,7 +122,8 @@ func replaceVars(format string, vars ...string) string { for _, replacement := range replacements { rplStr := replacement[0] varIndex, _ := strconv.Atoi(replacement[1]) - rplFmt := replacement[2] + rplColor := makeColor(replacement[2]) + rplFmt := replacement[3] // default format if not specified by user if rplFmt == "" { @@ -132,106 +132,16 @@ func replaceVars(format string, vars ...string) string { if strings.HasSuffix(rplFmt, "d") { // replace integers value, _ := strconv.ParseInt(vars[varIndex], 10, 64) - mutate := numMut2func[int64](replacement[3]) - format = strings.Replace(format, rplStr, fmt.Sprintf(rplFmt, mutate(value, vars)), 1) + mutate := numMut2func[int64](replacement[4]) + format = strings.Replace(format, rplStr, rplColor.Sprintf(rplFmt, mutate(value, vars)), 1) } else if strings.HasSuffix(rplFmt, "f") || strings.HasSuffix(rplFmt, "g") { // replace floats value, _ := strconv.ParseFloat(vars[varIndex], 64) - mutate := numMut2func[float64](replacement[3]) - format = strings.Replace(format, rplStr, fmt.Sprintf(rplFmt, mutate(value, vars)), 1) + mutate := numMut2func[float64](replacement[4]) + format = strings.Replace(format, rplStr, rplColor.Sprintf(rplFmt, mutate(value, vars)), 1) } else { // replace strings - format = strings.Replace(format, rplStr, fmt.Sprintf(rplFmt, vars[varIndex]), 1) + format = strings.Replace(format, rplStr, rplColor.Sprintf(rplFmt, vars[varIndex]), 1) } - } 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) - } - - numberParser := number_parser[T]() - - return func(value T, vars []string) T { - for _, mutator := range mutators { - var otherValue T - if mutator.Var { - other := numberParser(vars[mutator.Value]) - 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 - } -} - -func number_parser[T int64 | float64]() func(str string) T { - typeOfT := reflect.TypeOf(new(T)).Elem() - typeOfInt64 := reflect.TypeOf(new(int64)).Elem() - typeOfFloat64 := reflect.TypeOf(new(float64)).Elem() - - if typeOfT == typeOfInt64 { - return func(str string) T { - num, err := strconv.Atoi(str) - if err != nil { - panic("expected integer but found " + str) - } - return T(num) - } - } else if typeOfT == typeOfFloat64 { - return func(str string) T { - num, err := strconv.ParseFloat(str, 64) - if err != nil { - panic("expected float but found " + str) - } - return T(num) - } - } - - panic("invalid number type") -} diff --git a/mutator.go b/mutator.go index ad68b15..798f908 100644 --- a/mutator.go +++ b/mutator.go @@ -1,5 +1,10 @@ package main +import ( + "reflect" + "strconv" +) + type NumMutator struct { Op NumOperator Var bool @@ -29,3 +34,92 @@ func NewNumOperatorFromString(str string) NumOperator { panic("invalid number operator: " + str) } } + +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) + } + + numberParser := number_parser[T]() + + return func(value T, vars []string) T { + for _, mutator := range mutators { + var otherValue T + if mutator.Var { + other := numberParser(vars[mutator.Value]) + 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 + } +} + +func number_parser[T int64 | float64]() func(str string) T { + typeOfT := reflect.TypeOf(new(T)).Elem() + typeOfInt64 := reflect.TypeOf(new(int64)).Elem() + typeOfFloat64 := reflect.TypeOf(new(float64)).Elem() + + if typeOfT == typeOfInt64 { + return func(str string) T { + num, err := strconv.Atoi(str) + if err != nil { + panic("expected integer but found " + str) + } + return T(num) + } + } else if typeOfT == typeOfFloat64 { + return func(str string) T { + num, err := strconv.ParseFloat(str, 64) + if err != nil { + panic("expected float but found " + str) + } + return T(num) + } + } + + panic("invalid number type") +}