Skip to content

Commit 8f323fb

Browse files
authored
feat: support .tmpl template extension and add organization config (#176)
## What Updated the plugin generator to recognize .tmpl files instead of .txt as template files, added a required --organization flag, and refactored SetupTemplatingEnvironment to return a PluginConfig struct. Switched from html/template to text/template to prevent HTML-escaping in generated output. ## Why The plugin-generator-templates repo is moving from .txt to .tmpl extensions and introducing a {{ .Organization }} template variable (privateerproj/plugin-generator-templates#24). The html/template package was escaping characters like / in template output, breaking Go import paths. ## Notes - This is a breaking change to the GeneratePlugin and SetupTemplatingEnvironment public API signatures — the CLI repo (privateerproj/privateer) needs a corresponding update to add the --organization flag and adapt to PluginConfig - The text/template switch removes automatic HTML escaping — safe here since output is Go source and Makefiles, not HTML - README.md formatting fixes are included (markdown lint whitespace) Signed-off-by: jmeridth <jmeridth@gmail.com>
1 parent 57a12fc commit 8f323fb

File tree

2 files changed

+52
-32
lines changed

2 files changed

+52
-32
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The **Privateer SDK** provides the interface and utilities needed for developing
77
**For complete SDK documentation, visit [privateerproj.com/docs/developers/sdk/](https://privateerproj.com/docs/developers/sdk/)**
88

99
The website includes:
10+
1011
- Detailed SDK overview and components
1112
- Plugin development guides
1213
- API reference and examples
@@ -57,11 +58,13 @@ make build
5758
### Testing
5859

5960
Run all tests:
61+
6062
```bash
6163
make test
6264
```
6365

6466
Run tests with coverage:
67+
6568
```bash
6669
make testcov
6770
```
@@ -76,7 +79,7 @@ make testcov
7679

7780
## Project Structure
7881

79-
```
82+
```bash
8083
privateer-sdk/
8184
├── command/ # CLI command utilities
8285
├── config/ # Configuration management

command/generate-plugin.go

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7-
"html/template"
7+
"text/template"
88
"io"
99
"os"
1010
"path/filepath"
@@ -19,10 +19,20 @@ import (
1919
"github.com/privateerproj/privateer-sdk/utils"
2020
)
2121

22+
// PluginConfig holds the validated configuration for plugin generation.
23+
type PluginConfig struct {
24+
TemplatesDir string
25+
SourcePath string
26+
OutputDir string
27+
ServiceName string
28+
Organization string
29+
}
30+
2231
// CatalogData extends gemara.ControlCatalog with additional fields for plugin generation.
2332
type CatalogData struct {
2433
gemara.ControlCatalog
2534
ServiceName string
35+
Organization string
2636
Requirements []Req
2737
ApplicabilityCategories []string
2838
StrippedName string
@@ -35,11 +45,12 @@ type Req struct {
3545
}
3646

3747
// GeneratePlugin generates a plugin from a catalog file.
38-
func GeneratePlugin(logger hclog.Logger, templatesDir, sourcePath, outputDir, serviceName string) error {
48+
func GeneratePlugin(logger hclog.Logger, cfg PluginConfig) error {
3949
data := CatalogData{}
40-
data.ServiceName = serviceName
50+
data.ServiceName = cfg.ServiceName
51+
data.Organization = cfg.Organization
4152

42-
err := data.LoadFile("file://" + sourcePath)
53+
err := data.LoadFile("file://" + cfg.SourcePath)
4354
if err != nil {
4455
return err
4556
}
@@ -49,15 +60,15 @@ func GeneratePlugin(logger hclog.Logger, templatesDir, sourcePath, outputDir, se
4960
return err
5061
}
5162

52-
err = filepath.Walk(templatesDir,
63+
err = filepath.Walk(cfg.TemplatesDir,
5364
func(path string, info os.FileInfo, err error) error {
5465
if err != nil {
5566
return err
5667
}
5768
if !info.IsDir() {
58-
err = generateFileFromTemplate(data, path, templatesDir, outputDir, logger)
69+
err = generateFileFromTemplate(data, path, cfg.TemplatesDir, cfg.OutputDir, logger)
5970
if err != nil {
60-
logger.Error(fmt.Sprintf("Failed while writing in dir '%s': %s", outputDir, err))
71+
logger.Error(fmt.Sprintf("Failed while writing in dir '%s': %s", cfg.OutputDir, err))
6172
}
6273
} else if info.Name() == ".git" {
6374
return filepath.SkipDir
@@ -69,7 +80,7 @@ func GeneratePlugin(logger hclog.Logger, templatesDir, sourcePath, outputDir, se
6980
return fmt.Errorf("error walking through templates directory: %w", err)
7081
}
7182

72-
err = writeCatalogFile(&data.ControlCatalog, outputDir)
83+
err = writeCatalogFile(&data.ControlCatalog, cfg.OutputDir)
7384
if err != nil {
7485
return fmt.Errorf("failed to write catalog to file: %w", err)
7586
}
@@ -78,36 +89,43 @@ func GeneratePlugin(logger hclog.Logger, templatesDir, sourcePath, outputDir, se
7889
}
7990

8091
// SetupTemplatingEnvironment validates and sets up the environment for plugin generation.
81-
func SetupTemplatingEnvironment(logger hclog.Logger) (templatesDir, sourcePath, outputDir, serviceName string, err error) {
82-
sourcePath = viper.GetString("source-path")
83-
if sourcePath == "" {
84-
return "", "", "", "", fmt.Errorf("required: --source-path is required to generate a plugin from a control set from local file or URL")
92+
func SetupTemplatingEnvironment(logger hclog.Logger) (PluginConfig, error) {
93+
cfg := PluginConfig{}
94+
95+
cfg.SourcePath = viper.GetString("source-path")
96+
if cfg.SourcePath == "" {
97+
return cfg, fmt.Errorf("required: --source-path is required to generate a plugin from a control set from local file or URL")
98+
}
99+
100+
cfg.ServiceName = viper.GetString("service-name")
101+
if cfg.ServiceName == "" {
102+
return cfg, fmt.Errorf("required: --service-name is required to generate a plugin")
85103
}
86104

87-
serviceName = viper.GetString("service-name")
88-
if serviceName == "" {
89-
return "", "", "", "", fmt.Errorf("required: --service-name is required to generate a plugin")
105+
cfg.Organization = viper.GetString("organization")
106+
if cfg.Organization == "" {
107+
return cfg, fmt.Errorf("required: --organization is required to generate a plugin")
90108
}
91109

92110
if viper.GetString("local-templates") != "" {
93-
templatesDir = viper.GetString("local-templates")
111+
cfg.TemplatesDir = viper.GetString("local-templates")
94112
} else {
95-
templatesDir = filepath.Join(os.TempDir(), "privateer-templates")
96-
err = setupTemplatesDir(templatesDir, logger)
113+
cfg.TemplatesDir = filepath.Join(os.TempDir(), "privateer-templates")
114+
err := setupTemplatesDir(cfg.TemplatesDir, logger)
97115
if err != nil {
98-
return "", "", "", "", fmt.Errorf("error setting up templates directory: %w", err)
116+
return cfg, fmt.Errorf("error setting up templates directory: %w", err)
99117
}
100118
}
101119

102-
outputDir = viper.GetString("output-dir")
103-
logger.Trace(fmt.Sprintf("Generated plugin will be stored in this directory: %s", outputDir))
120+
cfg.OutputDir = viper.GetString("output-dir")
121+
logger.Trace(fmt.Sprintf("Generated plugin will be stored in this directory: %s", cfg.OutputDir))
104122

105-
err = os.MkdirAll(outputDir, os.ModePerm)
123+
err := os.MkdirAll(cfg.OutputDir, os.ModePerm)
106124
if err != nil {
107-
return "", "", "", "", err
125+
return cfg, err
108126
}
109127

110-
return templatesDir, sourcePath, outputDir, serviceName, nil
128+
return cfg, nil
111129
}
112130

113131
func setupTemplatesDir(templatesDir string, logger hclog.Logger) error {
@@ -138,16 +156,15 @@ func generateFileFromTemplate(data CatalogData, templatePath, templatesDir, outp
138156
return fmt.Errorf("error calculating relative path for %s: %w", templatePath, err)
139157
}
140158

141-
// If the template is not a text template, copy it over as-is (preserve mode)
142-
if filepath.Ext(templatePath) != ".txt" {
159+
// If the file is not a template, copy it over as-is (preserve mode)
160+
if filepath.Ext(templatePath) != ".tmpl" {
143161
return copyNonTemplateFile(templatePath, relativeFilepath, outputDir, logger)
144162
}
145163

146164
tmpl, err := template.New("plugin").Funcs(template.FuncMap{
147-
"as_text": func(in string) template.HTML {
148-
return template.HTML(
149-
strings.TrimSpace(
150-
strings.ReplaceAll(in, "\n", " ")))
165+
"as_text": func(in string) string {
166+
return strings.TrimSpace(
167+
strings.ReplaceAll(in, "\n", " "))
151168
},
152169
"default": func(in string, out string) string {
153170
if in != "" {
@@ -161,7 +178,7 @@ func generateFileFromTemplate(data CatalogData, templatePath, templatesDir, outp
161178
return fmt.Errorf("error parsing template file %s: %w", templatePath, err)
162179
}
163180

164-
outputPath := filepath.Join(outputDir, strings.TrimSuffix(relativeFilepath, ".txt"))
181+
outputPath := filepath.Join(outputDir, strings.TrimSuffix(relativeFilepath, ".tmpl"))
165182

166183
err = os.MkdirAll(filepath.Dir(outputPath), os.ModePerm)
167184
if err != nil {

0 commit comments

Comments
 (0)