Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package generate

import (
"codegen/cmd/generate/golang"
"codegen/cmd/generate/react"
"codegen/internal/flagkeys"

"github.com/spf13/cobra"
Expand All @@ -18,6 +19,7 @@ var Root = &cobra.Command{
func init() {
// Add subcommands.
Root.AddCommand(golang.Cmd)
Root.AddCommand(react.Cmd)

// Add flags.
Root.PersistentFlags().String(flagkeys.FlagManifestPath, "", "Path to the flag manifest.")
Expand Down
24 changes: 24 additions & 0 deletions cmd/generate/react/react.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package react

import (
"codegen/internal/generate"
"codegen/internal/generate/plugins/react"

"github.com/spf13/cobra"
)

// Cmd for "generate" command, handling code generation for flag accessors
var Cmd = &cobra.Command{
Use: "react",
Short: "Generate typesafe React Hooks.",
Long: `Generate typesafe React Hooks compatible with the OpenFeature React SDK.`,
RunE: func(cmd *cobra.Command, args []string) error {
params := react.Params{}
gen := react.NewGenerator(params)
err := generate.CreateFlagAccessors(gen)
return err
},
}

func init() {
}
4 changes: 2 additions & 2 deletions internal/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

// GenerateFile receives data for the Go template engine and outputs the contents to the file.
// Intended to be invoked by each language generator with appropiate data.
// Intended to be invoked by each language generator with appropriate data.
func GenerateFile(funcs template.FuncMap, contents string, data types.TmplDataInterface) error {
contentsTmpl, err := template.New("contents").Funcs(funcs).Parse(contents)
if err != nil {
Expand Down Expand Up @@ -47,7 +47,7 @@ func GenerateFile(funcs template.FuncMap, contents string, data types.TmplDataIn
return nil
}

// Takes as input a generator and outputs file with the appropiate flag accessors.
// Takes as input a generator and outputs file with the appropriate flag accessors.
// The flag data is taken from the provided flag manifest.
func CreateFlagAccessors(gen types.Generator) error {
bt, err := manifestutils.LoadData(viper.GetString(flagkeys.FlagManifestPath), gen.SupportedFlagTypes())
Expand Down
121 changes: 121 additions & 0 deletions internal/generate/plugins/react/react.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package react

import (
_ "embed"
"sort"
"strconv"
"text/template"

"codegen/internal/generate"
"codegen/internal/generate/types"

"github.com/iancoleman/strcase"
)

type TmplData struct {
*types.BaseTmplData
}

type genImpl struct {
}

// BaseTmplDataInfo provides the base template data for the codegen.
func (td *TmplData) BaseTmplDataInfo() *types.BaseTmplData {
return td.BaseTmplData
}

// supportedFlagTypes is the flag types supported by the Go template.
var supportedFlagTypes = map[types.FlagType]bool{
types.FloatType: true,
types.StringType: true,
types.IntType: true,
types.BoolType: true,
types.ObjectType: false,
}

func (*genImpl) SupportedFlagTypes() map[types.FlagType]bool {
return supportedFlagTypes
}

//go:embed react.tmpl
var reactTmpl string

func flagVarName(flagName string) string {
return strcase.ToCamel(flagName)
}

func flagInitParam(flagName string) string {
return strconv.Quote(flagName)
}

func flagAccessFunc(t types.FlagType) string {
switch t {
case types.IntType, types.FloatType:
return "useNumberFlagDetails"
case types.BoolType:
return "useBooleanFlagDetails"
case types.StringType:
return "useStringFlagDetails"
default:
return ""
}
}

func supportImports(flags []*types.FlagTmplData) []string {
imports := make(map[string]struct{})
for _, flag := range flags {
imports[flagAccessFunc(flag.Type)] = struct{}{}
}
var result []string
for k := range imports {
result = append(result, k)
}
sort.Strings(result)
return result
}

func defaultValueLiteral(flag *types.FlagTmplData) string {
switch flag.Type {
case types.StringType:
return strconv.Quote(flag.DefaultValue)
default:
return flag.DefaultValue
}
}

func typeString(flagType types.FlagType) string {
switch flagType {
case types.StringType:
return "string"
case types.IntType, types.FloatType:
return "number"
case types.BoolType:
return "boolean"
default:
return ""
}
}

func (g *genImpl) Generate(input types.Input) error {
funcs := template.FuncMap{
"FlagVarName": flagVarName,
"FlagInitParam": flagInitParam,
"FlagAccessFunc": flagAccessFunc,
"SupportImports": supportImports,
"DefaultValueLiteral": defaultValueLiteral,
"TypeString": typeString,
}
td := TmplData{
BaseTmplData: input.BaseData,
}
return generate.GenerateFile(funcs, reactTmpl, &td)
}

// Params are parameters for creating a Generator
type Params struct {
}

// NewGenerator creates a generator for React.
func NewGenerator(params Params) types.Generator {
return &genImpl{}
}
20 changes: 20 additions & 0 deletions internal/generate/plugins/react/react.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import {
{{- range $_, $p := SupportImports .Flags}}
{{$p}},
{{- end}}
} from "@openfeature/react-sdk";
{{ range .Flags}}
/**
* {{.Docs}}
*
* **Details:**
* - flag key: `{{ .Name}}`
* - default value: `{{ .DefaultValue}}`
* - type: `{{TypeString .Type}}`
*/
export const use{{FlagVarName .Name}} = (options: Parameters<typeof {{FlagAccessFunc .Type}}>[2]) => {
return {{FlagAccessFunc .Type}}({{FlagInitParam .Name}}, {{DefaultValueLiteral .}}, options);
};
{{ end}}