• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gatewayd-io / gatewayd / 11205007447

06 Oct 2024 08:36PM UTC coverage: 62.247% (-0.03%) from 62.274%
11205007447

push

github

web-flow
Refactor `interface{}` to `any` (#614)

86 of 106 new or added lines in 17 files covered. (81.13%)

2 existing lines in 1 file now uncovered.

4483 of 7202 relevant lines covered (62.25%)

10.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/plugin/plugin_scaffold.go
1
package plugin
2

3
import (
4
        "io/fs"
5
        "os"
6
        "path/filepath"
7
        "strings"
8
        "unicode"
9

10
        "github.com/cybercyst/go-scaffold/pkg/scaffold"
11
        gerr "github.com/gatewayd-io/gatewayd/errors"
12
        "golang.org/x/text/cases"
13
        "golang.org/x/text/language"
14
        "gopkg.in/yaml.v3"
15
)
16

17
const (
18
        FolderPermissions os.FileMode = 0o755
19
        FilePermissions   os.FileMode = 0o644
20
)
21

22
// Scaffold generates a gatewayd plugin based on the provided input file
23
// and stores the generated files in the specified output directory.
24
// The expected input file should be a YAML file containing the following fields:
25
//
26
// ```yaml
27
// remote_url: https://github.com/gatewayd-io/test-gatewayd-plugin
28
// version: 0.1
29
// description: This is test plugin
30
// license: MIT
31
// authors:
32
//   - GatewayD Team
33
func Scaffold(inputFile string, outputDir string) ([]string, error) {
×
34
        tempDir, err := os.MkdirTemp("", "gatewayd-plugin-template")
×
35
        if err != nil {
×
36
                return nil, gerr.ErrFailedToScaffoldPlugin.Wrap(err)
×
37
        }
×
38

39
        defer func() {
×
40
                os.RemoveAll(tempDir)
×
41
        }()
×
42

43
        // Copy the embedded directory and its contents as "go-scaffold" library
44
        // only accepts files on os filesystem
45
        err = fs.WalkDir(pluginTemplate, pluginTemplateRootDir, func(path string, dir fs.DirEntry, err error) error {
×
46
                if err != nil {
×
47
                        return gerr.ErrFailedToCopyEmbeddedFiles.Wrap(err)
×
48
                }
×
49

50
                relativePath, err := filepath.Rel(pluginTemplateRootDir, path)
×
51
                if err != nil {
×
52
                        return gerr.ErrFailedToCopyEmbeddedFiles.Wrap(err)
×
53
                }
×
54
                destPath := filepath.Join(tempDir, relativePath)
×
55

×
56
                if dir.IsDir() {
×
57
                        return os.MkdirAll(destPath, FolderPermissions)
×
58
                }
×
59

60
                fileContent, err := pluginTemplate.ReadFile(path)
×
61
                if err != nil {
×
62
                        return gerr.ErrFailedToCopyEmbeddedFiles.Wrap(err)
×
63
                }
×
64

65
                if err := os.MkdirAll(filepath.Dir(destPath), FolderPermissions); err != nil {
×
66
                        return gerr.ErrFailedToCopyEmbeddedFiles.Wrap(err)
×
67
                }
×
68

69
                return os.WriteFile(destPath, fileContent, FilePermissions)
×
70
        })
71
        if err != nil {
×
72
                return nil, gerr.ErrFailedToScaffoldPlugin.Wrap(err)
×
73
        }
×
74

75
        input, err := readScaffoldInputFile(inputFile)
×
76
        if err != nil {
×
77
                return nil, gerr.ErrFailedToScaffoldPlugin.Wrap(err)
×
78
        }
×
79

80
        var pluginName string
×
81
        if url, ok := input["remote_url"].(string); ok {
×
82
                pluginName = getLastSegment(url)
×
83
        } else {
×
84
                return nil, gerr.ErrFailedToScaffoldPlugin.Wrap(err)
×
85
        }
×
86

87
        input["plugin_name"] = pluginName
×
88
        input["pascal_case_plugin_name"] = toPascalCase(pluginName)
×
89
        // set go_mod as template variable because the go.mod file is not embedable.
×
90
        // so we would name it as {{ go_mod }} and rename it to go.mod when scaffolding to circumvent this issue
×
91
        input["go_mod"] = "go.mod"
×
92

×
93
        template, err := scaffold.Download(tempDir)
×
94
        if err != nil {
×
95
                return nil, gerr.ErrFailedToScaffoldPlugin.Wrap(err)
×
96
        }
×
97

98
        metadata, err := scaffold.Generate(template, &input, outputDir)
×
99
        if err != nil {
×
100
                return nil, gerr.ErrFailedToScaffoldPlugin.Wrap(err)
×
101
        }
×
102

103
        metadataYaml, err := yaml.Marshal(metadata)
×
104
        if err != nil {
×
105
                return nil, gerr.ErrFailedToScaffoldPlugin.Wrap(err)
×
106
        }
×
107

108
        err = os.WriteFile(filepath.Join(outputDir, ".metadata.yaml"), metadataYaml, FilePermissions)
×
109
        if err != nil {
×
110
                return nil, gerr.ErrFailedToScaffoldPlugin.Wrap(err)
×
111
        }
×
112

113
        return *metadata.CreatedFiles, nil
×
114
}
115

116
// readScaffoldInputFile reads the template input file in YAML format and
117
// returns a map containing the parsed data.
118
//
119
// This function opens the provided input file path, reads its contents, and unmarshals
120
// the YAML data into a Go map[string]any. It returns the parsed map and any encountered error.
NEW
121
func readScaffoldInputFile(inputFilePath string) (map[string]any, error) {
×
122
        inputBytes, err := os.ReadFile(inputFilePath)
×
123
        if err != nil {
×
124
                return nil, gerr.ErrFailedToReadPluginScaffoldInputFile.Wrap(err)
×
125
        }
×
126

NEW
127
        inputJSON := make(map[string]any)
×
128
        err = yaml.Unmarshal(inputBytes, &inputJSON)
×
129
        if err != nil {
×
130
                return nil, gerr.ErrFailedToReadPluginScaffoldInputFile.Wrap(err)
×
131
        }
×
132

133
        return inputJSON, nil
×
134
}
135

136
// toPascalCase converts a string to PascalCase format, suitable for use as a plugin class name in templates.
137
func toPascalCase(input string) string {
×
138
        trimmed := strings.TrimSpace(input)
×
139
        words := strings.FieldsFunc(trimmed, func(r rune) bool {
×
140
                return r == ' ' || r == '-'
×
141
        })
×
142
        var pascalCase string
×
143
        for _, word := range words {
×
144
                caser := cases.Title(language.English)
×
145
                pascalCase += caser.String(word)
×
146
        }
×
147

148
        // Remove any non-alphanumeric characters except underscores
149
        cleaned := strings.Map(func(r rune) rune {
×
150
                if unicode.IsLetter(r) || unicode.IsDigit(r) {
×
151
                        return r
×
152
                }
×
153
                if r == '_' {
×
154
                        return r
×
155
                }
×
156
                return -1
×
157
        }, pascalCase)
158

159
        return cleaned
×
160
}
161

162
// getLastSegment extracts the last path segment from a given string, assuming a forward slash ('/') separator.
163
func getLastSegment(input string) string {
×
164
        parts := strings.Split(input, "/")
×
165
        if len(parts) == 0 {
×
166
                return ""
×
167
        }
×
168
        return parts[len(parts)-1]
×
169
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc