|
| 1 | +# Custom Templates |
| 2 | + |
| 3 | +The OpenFeature CLI supports custom templates that allow you to customize the generated code output. This is useful when you need to: |
| 4 | + |
| 5 | +- Modify the structure of generated code to fit your project conventions |
| 6 | +- Add custom imports, utilities, or wrappers |
| 7 | +- Change naming conventions or code style |
| 8 | +- Generate code for frameworks or libraries not yet supported |
| 9 | + |
| 10 | +## Usage |
| 11 | + |
| 12 | +Use the `--template` flag with any generate subcommand: |
| 13 | + |
| 14 | +```bash |
| 15 | +openfeature generate go --template ./my-custom-template.tmpl |
| 16 | +openfeature generate react --template ./custom-react.tmpl |
| 17 | +openfeature generate nodejs --template ./custom-nodejs.tmpl |
| 18 | +``` |
| 19 | + |
| 20 | +## Getting Started |
| 21 | + |
| 22 | +The easiest way to create a custom template is to start from an existing one: |
| 23 | + |
| 24 | +1. Copy the default template for your target language from the CLI source: |
| 25 | + - Go: `internal/generators/golang/golang.tmpl` |
| 26 | + - React: `internal/generators/react/react.tmpl` |
| 27 | + - Node.js: `internal/generators/nodejs/nodejs.tmpl` |
| 28 | + - Python: `internal/generators/python/python.tmpl` |
| 29 | + - C#: `internal/generators/csharp/csharp.tmpl` |
| 30 | + - Java: `internal/generators/java/java.tmpl` |
| 31 | + - NestJS: `internal/generators/nestjs/nestjs.tmpl` |
| 32 | + |
| 33 | +2. Modify the template to suit your needs |
| 34 | + |
| 35 | +3. Use the `--template` flag to generate code with your custom template |
| 36 | + |
| 37 | +## Template Syntax |
| 38 | + |
| 39 | +Custom templates use Go's [text/template](https://pkg.go.dev/text/template) package. Refer to the official documentation for the full syntax, including conditionals, loops, and pipelines. |
| 40 | + |
| 41 | +## Template Data |
| 42 | + |
| 43 | +Templates have access to the following data structure: |
| 44 | + |
| 45 | +```go |
| 46 | +type TemplateData struct { |
| 47 | + Flagset struct { |
| 48 | + Flags []Flag |
| 49 | + } |
| 50 | + Params struct { |
| 51 | + OutputPath string |
| 52 | + Custom any // Language-specific parameters |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +type Flag struct { |
| 57 | + Key string // The flag key (e.g., "enable-feature") |
| 58 | + Type FlagType // The flag type (boolean, string, integer, float, object) |
| 59 | + Description string // Optional description of the flag |
| 60 | + DefaultValue any // The default value for the flag |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +### Language-Specific Parameters |
| 65 | + |
| 66 | +Some generators provide additional parameters in `.Params.Custom`: |
| 67 | + |
| 68 | +**Go:** |
| 69 | +- `.Params.Custom.GoPackage` - The Go package name |
| 70 | +- `.Params.Custom.CLIVersion` - The CLI version used for generation |
| 71 | + |
| 72 | +**C#:** |
| 73 | +- `.Params.Custom.Namespace` - The C# namespace |
| 74 | + |
| 75 | +**Java:** |
| 76 | +- `.Params.Custom.JavaPackage` - The Java package name |
| 77 | + |
| 78 | +## Template Functions |
| 79 | + |
| 80 | +### Common Functions (Available in All Templates) |
| 81 | + |
| 82 | +These functions are available in all templates: |
| 83 | + |
| 84 | +| Function | Description | Example | |
| 85 | +|----------|-------------|---------| |
| 86 | +| `ToPascal` | Convert to PascalCase | `{{ .Key \| ToPascal }}` → `EnableFeature` | |
| 87 | +| `ToCamel` | Convert to camelCase | `{{ .Key \| ToCamel }}` → `enableFeature` | |
| 88 | +| `ToKebab` | Convert to kebab-case | `{{ .Key \| ToKebab }}` → `enable-feature` | |
| 89 | +| `ToScreamingKebab` | Convert to SCREAMING-KEBAB-CASE | `{{ .Key \| ToScreamingKebab }}` → `ENABLE-FEATURE` | |
| 90 | +| `ToSnake` | Convert to snake_case | `{{ .Key \| ToSnake }}` → `enable_feature` | |
| 91 | +| `ToScreamingSnake` | Convert to SCREAMING_SNAKE_CASE | `{{ .Key \| ToScreamingSnake }}` → `ENABLE_FEATURE` | |
| 92 | +| `ToUpper` | Convert to UPPERCASE | `{{ .Key \| ToUpper }}` → `ENABLE-FEATURE` | |
| 93 | +| `ToLower` | Convert to lowercase | `{{ .Key \| ToLower }}` → `enable-feature` | |
| 94 | +| `Quote` | Add double quotes | `{{ .Key \| Quote }}` → `"enable-feature"` | |
| 95 | +| `QuoteString` | Quote if string type | `{{ .DefaultValue \| QuoteString }}` | |
| 96 | + |
| 97 | +### Go-Specific Functions |
| 98 | + |
| 99 | +| Function | Description | |
| 100 | +|----------|-------------| |
| 101 | +| `OpenFeatureType` | Convert flag type to OpenFeature method name (`Boolean`, `String`, `Int`, `Float`, `Object`) | |
| 102 | +| `TypeString` | Convert flag type to Go type (`bool`, `string`, `int64`, `float64`, `map[string]any`) | |
| 103 | +| `SupportImports` | Generate required imports based on flags | |
| 104 | +| `ToMapLiteral` | Convert object value to Go map literal | |
| 105 | + |
| 106 | +### React/Node.js/NestJS-Specific Functions |
| 107 | + |
| 108 | +| Function | Description | |
| 109 | +|----------|-------------| |
| 110 | +| `OpenFeatureType` | Convert flag type to TypeScript type (`boolean`, `string`, `number`, `object`) | |
| 111 | +| `ToJSONString` | Convert value to JSON string | |
| 112 | + |
| 113 | +### Python-Specific Functions |
| 114 | + |
| 115 | +| Function | Description | |
| 116 | +|----------|-------------| |
| 117 | +| `OpenFeatureType` | Convert flag type to Python type (`bool`, `str`, `int`, `float`, `object`) | |
| 118 | +| `TypedGetMethodSync` | Get synchronous getter method name | |
| 119 | +| `TypedGetMethodAsync` | Get async getter method name | |
| 120 | +| `TypedDetailsMethodSync` | Get synchronous details method name | |
| 121 | +| `TypedDetailsMethodAsync` | Get async details method name | |
| 122 | +| `PythonBoolLiteral` | Convert boolean to Python literal (`True`/`False`) | |
| 123 | +| `ToPythonDict` | Convert object value to Python dict literal | |
| 124 | + |
| 125 | +### C#-Specific Functions |
| 126 | + |
| 127 | +| Function | Description | |
| 128 | +|----------|-------------| |
| 129 | +| `OpenFeatureType` | Convert flag type to C# type (`bool`, `string`, `int`, `double`, `object`) | |
| 130 | +| `FormatDefaultValue` | Format default value for C# | |
| 131 | +| `ToCSharpDict` | Convert object value to C# dictionary literal | |
| 132 | + |
| 133 | +### Java-Specific Functions |
| 134 | + |
| 135 | +| Function | Description | |
| 136 | +|----------|-------------| |
| 137 | +| `OpenFeatureType` | Convert flag type to Java type (`Boolean`, `String`, `Integer`, `Double`, `Object`) | |
| 138 | +| `FormatDefaultValue` | Format default value for Java | |
| 139 | +| `ToMapLiteral` | Convert object value to Java Map literal | |
| 140 | + |
| 141 | +## Example: Simple Go Template |
| 142 | + |
| 143 | +Here's a minimal example of a custom Go template: |
| 144 | + |
| 145 | +```go |
| 146 | +// Code generated by OpenFeature CLI with custom template |
| 147 | +package {{ .Params.Custom.GoPackage }} |
| 148 | + |
| 149 | +import ( |
| 150 | + "context" |
| 151 | + "github.com/open-feature/go-sdk/openfeature" |
| 152 | +) |
| 153 | + |
| 154 | +var client = openfeature.NewDefaultClient() |
| 155 | + |
| 156 | +{{- range .Flagset.Flags }} |
| 157 | +// Get{{ .Key | ToPascal }} returns the value of the "{{ .Key }}" flag. |
| 158 | +// {{ if .Description }}{{ .Description }}{{ end }} |
| 159 | +func Get{{ .Key | ToPascal }}(ctx context.Context, evalCtx openfeature.EvaluationContext) {{ .Type | TypeString }} { |
| 160 | + return client.{{ .Type | OpenFeatureType }}(ctx, {{ .Key | Quote }}, {{ .DefaultValue | QuoteString }}, evalCtx) |
| 161 | +} |
| 162 | +{{- end }} |
| 163 | +``` |
| 164 | + |
| 165 | +## Example: Custom React Template |
| 166 | + |
| 167 | +Here's an example that generates simple hooks without suspense: |
| 168 | + |
| 169 | +```typescript |
| 170 | +import { useFlag } from "@openfeature/react-sdk"; |
| 171 | + |
| 172 | +{{ range .Flagset.Flags }} |
| 173 | +/** |
| 174 | + * {{ if .Description }}{{ .Description }}{{ else }}Feature flag{{ end }} |
| 175 | + * Default: {{ .DefaultValue }} |
| 176 | + */ |
| 177 | +export const use{{ .Key | ToPascal }} = () => { |
| 178 | + return useFlag({{ .Key | Quote }}, {{ .DefaultValue | QuoteString }}); |
| 179 | +}; |
| 180 | +{{ end }} |
| 181 | +``` |
| 182 | + |
| 183 | +## Tips |
| 184 | + |
| 185 | +1. **Test incrementally**: Make small changes and test the output frequently |
| 186 | +2. **Use `--output` flag**: Direct output to a test directory while developing your template |
| 187 | +3. **Preserve formatting**: The generators apply language-specific formatters after template execution (e.g., `gofmt` for Go) |
| 188 | +4. **Handle edge cases**: Consider empty flag lists, missing descriptions, and different flag types |
| 189 | +5. **Check the source**: Review the default templates in the CLI source for comprehensive examples |
0 commit comments