cli/internal/generators/python/python.go

166 lines
3.6 KiB
Go

package python
import (
_ "embed"
"encoding/json"
"fmt"
"maps"
"slices"
"strings"
"text/template"
"github.com/open-feature/cli/internal/flagset"
"github.com/open-feature/cli/internal/generators"
)
type PythonGenerator struct {
generators.CommonGenerator
}
type Params struct {
}
//go:embed python.tmpl
var pythonTmpl string
func openFeatureType(t flagset.FlagType) string {
switch t {
case flagset.IntType:
return "int"
case flagset.FloatType:
return "float"
case flagset.BoolType:
return "bool"
case flagset.StringType:
return "str"
default:
return "object"
}
}
func methodType(flagType flagset.FlagType) string {
switch flagType {
case flagset.StringType:
return "string"
case flagset.IntType:
return "integer"
case flagset.BoolType:
return "boolean"
case flagset.FloatType:
return "float"
case flagset.ObjectType:
return "object"
default:
panic("unsupported flag type")
}
}
func typedGetMethodSync(flagType flagset.FlagType) string {
return "get_" + methodType(flagType) + "_value"
}
func typedGetMethodAsync(flagType flagset.FlagType) string {
return "get_" + methodType(flagType) + "_value_async"
}
func typedDetailsMethodSync(flagType flagset.FlagType) string {
return "get_" + methodType(flagType) + "_details"
}
func typedDetailsMethodAsync(flagType flagset.FlagType) string {
return "get_" + methodType(flagType) + "_details_async"
}
func pythonBoolLiteral(value any) any {
if v, ok := value.(bool); ok {
if v {
return "True"
}
return "False"
}
return value
}
func toPythonDict(value any) string {
assertedMap, ok := value.(map[string]any)
if !ok {
return "None"
}
// To have a determined order of the object for comparison
keys := slices.Sorted(maps.Keys(assertedMap))
var builder strings.Builder
builder.WriteString("{")
for index, key := range keys {
if index != 0 {
builder.WriteString(", ")
}
val := assertedMap[key]
builder.WriteString(fmt.Sprintf(`%q: %s`, key, formatNestedValue(val)))
}
builder.WriteString("}")
return builder.String()
}
func formatNestedValue(value any) string {
switch val := value.(type) {
case string:
return fmt.Sprintf("%q", val)
case bool:
return fmt.Sprintf(pythonBoolLiteral(val).(string))
case int, int64, float64:
return fmt.Sprintf("%v", val)
case map[string]any:
return toPythonDict(val)
case []any:
var sliceBuilder strings.Builder
sliceBuilder.WriteString("[")
for index, elem := range val {
if index > 0 {
sliceBuilder.WriteString(", ")
}
sliceBuilder.WriteString(formatNestedValue(elem))
}
sliceBuilder.WriteString("]")
return sliceBuilder.String()
default:
jsonBytes, err := json.Marshal(val)
if err != nil {
return "None"
}
return strings.ReplaceAll(string(jsonBytes), "null", "None")
}
}
func (g *PythonGenerator) Generate(params *generators.Params[Params]) error {
funcs := template.FuncMap{
"OpenFeatureType": openFeatureType,
"TypedGetMethodSync": typedGetMethodSync,
"TypedGetMethodAsync": typedGetMethodAsync,
"TypedDetailsMethodSync": typedDetailsMethodSync,
"TypedDetailsMethodAsync": typedDetailsMethodAsync,
"PythonBoolLiteral": pythonBoolLiteral,
"ToPythonDict": toPythonDict,
}
newParams := &generators.Params[any]{
OutputPath: params.OutputPath,
Custom: Params{},
}
return g.GenerateFile(funcs, pythonTmpl, newParams, "openfeature.py")
}
// NewGenerator creates a generator for Python.
func NewGenerator(fs *flagset.Flagset) *PythonGenerator {
return &PythonGenerator{
CommonGenerator: *generators.NewGenerator(fs, map[flagset.FlagType]bool{}),
}
}