Skip to content

Commit

Permalink
add basic react support
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
  • Loading branch information
beeme1mr committed Oct 14, 2024
1 parent 850c694 commit d3febc4
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 2 deletions.
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}}

0 comments on commit d3febc4

Please sign in to comment.