chore: allow decorator to split functions (#26357)

This commit is contained in:
andig 2026-01-02 12:28:13 +01:00 • committed by GitHub
parent 1e0539ec7d
commit 30b1e135e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 903 additions and 11264 deletions

View file

@ -32,6 +32,9 @@ import (
//go:embed decorate.tpl
var srcTmpl string
//go:embed header.tpl
var header string
type dynamicType struct {
typ, function, signature string
}
@ -85,7 +88,7 @@ func hasIntersection[T comparable](a, b []T) bool {
return false
}
func generate(out io.Writer, packageName, functionName, baseType string, dynamicTypes ...dynamicType) error {
func generate(out io.Writer, functionName, baseType string, dynamicTypes ...dynamicType) error {
types := make(map[string]typeStruct, len(dynamicTypes))
combos := make([]string, 0)
@ -186,15 +189,12 @@ COMBO:
}
vars := struct {
API string
Package, Function string
Function string
BaseType, ShortBase string
ReturnType string
Types map[string]typeStruct
Combinations [][]string
}{
API: "github.com/evcc-io/evcc/api",
Package: packageName,
Function: functionName,
BaseType: baseType,
ShortBase: shortBase,
@ -206,6 +206,11 @@ COMBO:
return tmpl.Execute(out, vars)
}
type decorationSet struct {
function, base, ret string
types []string
}
var (
target = pflag.StringP("out", "o", "", "output file")
pkg = pflag.StringP("package", "p", "", "package name")
@ -223,13 +228,17 @@ func Usage() {
pflag.PrintDefaults()
}
func parseFile(file string, function, basetype, returntype *string, types *[]string) error {
func parseFile(file string) ([]decorationSet, error) {
var res []decorationSet
f, err := os.Open(file)
if err != nil {
return err
return nil, err
}
defer f.Close()
var current decorationSet
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
@ -242,20 +251,29 @@ func parseFile(file string, function, basetype, returntype *string, types *[]str
switch segs[0] {
case "function":
*function = segs[1]
// must be first
if current.function != "" {
res = append(res, current)
current = decorationSet{}
}
current.function = segs[1]
case "basetype":
*basetype = segs[1]
current.base = segs[1]
case "returntype":
*returntype = segs[1]
current.ret = segs[1]
case "type":
*types = append(*types, segs[1])
current.types = append(current.types, segs[1])
default:
panic("invalid directive //evcc:" + segs[0])
}
}
}
return scanner.Err()
if current.function != "" {
res = append(res, current)
}
return res, scanner.Err()
}
func main() {
@ -274,32 +292,22 @@ func main() {
pkg = &gopkg
}
sets := []decorationSet{{*function, *base, *ret, *types}}
if *function == "" {
if err := parseFile(gofile, function, base, ret, types); err != nil {
all, err := parseFile(gofile)
if err != nil {
fmt.Println(err)
os.Exit(2)
}
sets = all
}
if *base == "" || *pkg == "" || len(*types) == 0 {
if *pkg == "" || len(sets) == 0 || sets[0].base == "" || len(sets[0].types) == 0 {
Usage()
os.Exit(2)
}
var dynamicTypes []dynamicType
for _, v := range *types {
split := strings.SplitN(v, ",", 3)
dt := dynamicType{split[0], split[1], split[2]}
dynamicTypes = append(dynamicTypes, dt)
}
var buf bytes.Buffer
if err := generate(&buf, *pkg, *function, *base, dynamicTypes...); err != nil {
fmt.Println(err)
os.Exit(2)
}
generated := strings.TrimSpace(buf.String()) + "\n"
var out io.Writer = os.Stdout
var name string
@ -319,9 +327,29 @@ func main() {
out = dst
}
formatted, err := format.Source([]byte(generated))
generated := new(bytes.Buffer)
fmt.Fprintln(generated, strings.ReplaceAll(header, "{{.Package}}", *pkg))
for _, set := range sets {
var dynamicTypes []dynamicType
for _, v := range set.types {
split := strings.SplitN(v, ",", 3)
dt := dynamicType{split[0], split[1], split[2]}
dynamicTypes = append(dynamicTypes, dt)
}
var buf bytes.Buffer
if err := generate(&buf, set.function, set.base, dynamicTypes...); err != nil {
fmt.Println(err)
os.Exit(2)
}
fmt.Fprintln(generated, buf.String())
}
formatted, err := format.Source(generated.Bytes())
if err != nil {
formatted = []byte(generated)
formatted = generated.Bytes()
}
formatted, err = imports.Process(name, formatted, nil)

View file

@ -1,11 +1,3 @@
package {{.Package}}
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
import (
"{{.API}}"
)
{{define "case"}}
{{- $combo := .Combo}}
{{- $prefix := .Prefix}}

7
cmd/decorate/header.tpl Normal file
View file

@ -0,0 +1,7 @@
package {{.Package}}
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
import (
"github.com/evcc-io/evcc/api"
)

View file

@ -14,7 +14,24 @@ func init() {
registry.AddCtx(api.Custom, NewConfigurableFromConfig)
}
//go:generate go tool decorate -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.PhasePowers,Powers,func() (float64, float64, float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryCapacity,Capacity,func() float64" -t "api.BatterySocLimiter,GetSocLimits,func() (float64, float64)" -t "api.BatteryPowerLimiter,GetPowerLimits,func() (float64, float64)" -t "api.BatteryController,SetBatteryMode,func(api.BatteryMode) error" -t "api.MaxACPowerGetter,MaxACPower,func() float64"
//go:generate go tool decorate
//evcc:function decorateMeter
//evcc:basetype api.Meter
//evcc:type api.MeterEnergy,TotalEnergy,func() (float64, error)
//evcc:type api.PhaseCurrents,Currents,func() (float64, float64, float64, error)
//evcc:type api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)
//evcc:type api.PhasePowers,Powers,func() (float64, float64, float64, error)
//evcc:type api.MaxACPowerGetter,MaxACPower,func() float64
//evcc:function decorateMeterBattery
//evcc:basetype api.Meter
//evcc:type api.MeterEnergy,TotalEnergy,func() (float64, error)
//evcc:type api.Battery,Soc,func() (float64, error)
//evcc:type api.BatteryCapacity,Capacity,func() float64
//evcc:type api.BatterySocLimiter,GetSocLimits,func() (float64, float64)
//evcc:type api.BatteryPowerLimiter,GetPowerLimits,func() (float64, float64)
//evcc:type api.BatteryController,SetBatteryMode,func(api.BatteryMode) error
// NewConfigurableFromConfig creates api.Meter from config
func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.Meter, error) {
@ -83,13 +100,18 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]any) (api.M
}
}
res := m.Decorate(
energyG, currentsG, voltagesG, powersG,
socG, cc.batteryCapacity.Decorator(), cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(), batModeS,
cc.pvMaxACPower.Decorator(),
)
if socG != nil {
return m.DecorateBattery(
energyG,
socG, cc.batteryCapacity.Decorator(),
cc.batterySocLimits.Decorator(), cc.batteryPowerLimits.Decorator(),
batModeS,
), nil
}
return res, nil
return m.Decorate(
energyG, currentsG, voltagesG, powersG, cc.pvMaxACPower.Decorator(),
), nil
}
// NewConfigurable creates a new meter
@ -109,19 +131,28 @@ type Meter struct {
func (m *Meter) Decorate(
totalEnergy func() (float64, error),
currents, voltages, powers func() (float64, float64, float64, error),
batterySoc func() (float64, error),
batteryCapacity func() float64,
batterySocLimits, batteryPowerLimits func() (float64, float64),
setBatteryMode func(api.BatteryMode) error,
maxACPower func() float64,
) api.Meter {
return decorateMeter(m,
totalEnergy, currents, voltages, powers,
batterySoc, batteryCapacity, batterySocLimits, batteryPowerLimits, setBatteryMode,
maxACPower,
)
}
func (m *Meter) DecorateBattery(
totalEnergy func() (float64, error),
soc func() (float64, error), capacity func() float64,
socLimits, powerLimits func() (float64, float64),
setMode func(api.BatteryMode) error,
) api.Meter {
return decorateMeterBattery(m,
totalEnergy,
soc, capacity,
socLimits, powerLimits,
setMode,
)
}
// CurrentPower implements the api.Meter interface
func (m *Meter) CurrentPower() (float64, error) {
return m.currentPowerG()

View file

@ -70,7 +70,11 @@ func NewMovingAverageFromConfig(ctx context.Context, other map[string]any) (api.
powers = m.Powers
}
return meter.Decorate(totalEnergy, currents, voltages, powers, batterySoc, cc.Meter.batteryCapacity.Decorator(), nil, nil, nil, nil), nil
if batterySoc != nil {
return meter.DecorateBattery(totalEnergy, batterySoc, cc.Meter.batteryCapacity.Decorator(), nil, nil, nil), nil
}
return meter.Decorate(totalEnergy, currents, voltages, powers, nil), nil
}
type MovingAverage struct {

File diff suppressed because it is too large Load diff

View file

@ -8,22 +8,33 @@ import (
"github.com/stretchr/testify/require"
)
func TestACPower(t *testing.T) {
func TestPV(t *testing.T) {
m, err := NewConfigurableFromConfig(t.Context(), map[string]any{
"power": map[string]any{
"source": "const",
"value": 1000,
},
"maxacpower": 1000,
})
require.NoError(t, err)
// must not have soc/capacity
assert.Implements(t, new(api.MaxACPowerGetter), m)
}
func TestBattery(t *testing.T) {
m, err := NewConfigurableFromConfig(t.Context(), map[string]any{
"power": map[string]any{
"source": "const",
"value": 1000,
},
"capacity": 23,
"soc": map[string]any{
"source": "const",
"value": 47,
},
"maxacpower": 1000,
"power": map[string]any{
"source": "const",
"value": 1000,
},
})
require.NoError(t, err)
_, ok := m.(api.BatteryCapacity)
assert.True(t, ok, "api.BatteryCapacity")
_, ok = m.(api.MaxACPowerGetter)
assert.True(t, ok, "api.MaxACPowerGetter")
assert.Implements(t, new(api.Battery), m)
assert.Implements(t, new(api.BatteryCapacity), m)
}

View file

@ -139,7 +139,9 @@ func NewOpenWBFromConfig(other map[string]any) (api.Meter, error) {
return nil, err
}
res := m.Decorate(nil, currents, nil, nil, soc, capacity, nil, nil, nil, nil)
if strings.ToLower(cc.Usage) == "battery" {
return m.DecorateBattery(nil, soc, capacity, nil, nil, nil), nil
}
return res, nil
return m.Decorate(nil, currents, nil, nil, nil), nil
}