Skip to content

Commit e00ed70

Browse files
authored
Merge pull request #101 from dengsh12/NLB5024
Command line code generator for supporting files
2 parents 10437d9 + deb8b05 commit e00ed70

File tree

24 files changed

+830
-9
lines changed

24 files changed

+830
-9
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,49 @@ func main() {
7373
}
7474
```
7575

76+
# Generate support for third-party modules
77+
This is an example that takes the path of a third-party module source code to generate support for it. Assume the source code path of that module is `./src`. You can call `go run cmd/generate/main.go ./src`. The stdout will be like
78+
79+
```go
80+
/**
81+
* Copyright (c) F5, Inc.
82+
*
83+
* This source code is licensed under the Apache License, Version 2.0 license found in the
84+
* LICENSE file in the root directory of this source tree.
85+
*/
86+
87+
// Code generated by generator; DO NOT EDIT.
88+
// All the definitions are extracted from the source code
89+
// Each bit mask describes these behaviors:
90+
// - how many arguments the directive can take
91+
// - whether or not it is a block directive
92+
// - whether this is a flag (takes one argument that's either "on" or "off")
93+
// - which contexts it's allowed to be in
94+
95+
package crossplane
96+
97+
var directives = map[string][]uint{
98+
"my_directive_1": {
99+
bitmask01|bitmask02|...,
100+
bitmask11|bitmask12|...,
101+
...
102+
},
103+
"my_directive_2": {
104+
bitmask01|bitmask02|...,
105+
bitmask11|bitmask12|...,
106+
...
107+
},
108+
}
109+
110+
// Match is a matchFunc for parsing an NGINX config that contains the
111+
// preceding directives.
112+
func Match(directive string) ([]uint, bool) {
113+
m, ok := directives[directive]
114+
return m, ok
115+
}
116+
```
117+
You can redirect the stdout into a `.go` file, and pass the generated `matchFunc` to `ParseOptions.DirectiveSources` when invoking `Parse`.
118+
76119
## Contributing
77120

78121
If you'd like to contribute to the project, please read our [Contributing guide](CONTRIBUTING.md).

cmd/generate/main.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) F5, Inc.
3+
*
4+
* This source code is licensed under the Apache License, Version 2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package main
9+
10+
import (
11+
"flag"
12+
"log"
13+
"os"
14+
15+
"github.com/nginxinc/nginx-go-crossplane/internal/generator"
16+
)
17+
18+
func main() {
19+
var (
20+
sourceCodePath = flag.String("src-path", "",
21+
"the path of source code your want to generate support from, it can be either a file or a directory. (required)")
22+
)
23+
flag.Parse()
24+
err := generator.Generate(*sourceCodePath, os.Stdout)
25+
if err != nil {
26+
log.Fatal(err)
27+
}
28+
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/kr/pretty v0.3.1 // indirect
1515
github.com/pmezard/go-difflib v1.0.0 // indirect
1616
golang.org/x/mod v0.10.0 // indirect
17-
golang.org/x/sys v0.7.0 // indirect
17+
golang.org/x/net v0.24.0 // indirect
18+
golang.org/x/sys v0.19.0 // indirect
1819
gopkg.in/yaml.v3 v3.0.1 // indirect
1920
)

go.sum

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
2727
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
2828
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
2929
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
30-
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
30+
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
31+
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
3132
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
32-
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
33-
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
34-
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
33+
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
34+
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
35+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
3536
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
3637
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
3738
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/generator/generator.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright (c) F5, Inc.
3+
*
4+
* This source code is licensed under the Apache License, Version 2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package generator
9+
10+
import (
11+
"io"
12+
)
13+
14+
// Generate receives a string sourcePath and an io.Writer writer. It will
15+
// extract all the directives definitions from the .c and .cpp files in
16+
// sourcePath and its subdirectories, then output the corresponding directive
17+
// masks map named "directives" and matchFunc named "Match" via writer.
18+
func Generate(sourcePath string, writer io.Writer) error {
19+
return genFromSrcCode(sourcePath, "directives", "Match", writer)
20+
}

internal/generator/generator_util.go

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/**
2+
* Copyright (c) F5, Inc.
3+
*
4+
* This source code is licensed under the Apache License, Version 2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package generator
9+
10+
import (
11+
_ "embed"
12+
"errors"
13+
"fmt"
14+
"html/template"
15+
"io"
16+
"io/fs"
17+
"os"
18+
"path/filepath"
19+
"regexp"
20+
"strings"
21+
)
22+
23+
// A mask is a list of string, includes several variable names,
24+
// which specify a behavior of a directive.
25+
// An example is []string{"ngxHTTPMainConf", "ngxConfFlag",}.
26+
// A directive can have several masks.
27+
type mask []string
28+
29+
type supportFileTmplStruct struct {
30+
Directive2Masks map[string][]mask
31+
MapVariableName string
32+
MatchFnName string
33+
}
34+
35+
var (
36+
// Extract single directive definition block
37+
// static ngx_command_t {name}[] = {definition}
38+
// this regex extracts {name} and {definition}.
39+
directivesDefineBlockExtracter = regexp.MustCompile(`ngx_command_t\s+(\w+)\[\]\s*=\s*{(.*?)}\s*;`)
40+
41+
// Extract one directive definition and attributes from extracted block
42+
// { ngx_string({directive_name}),
43+
// {bitmask1|bitmask2|...},
44+
// ... },
45+
// this regex extracts {directive_name} and {bitmask1|bitmask2|...}.
46+
singleDirectiveExtracter = regexp.MustCompile(`ngx_string\("(.*?)"\).*?,(.*?),`)
47+
48+
singleLineCommentExtracter = regexp.MustCompile(`//.*`)
49+
50+
multiLineCommentExtracter = regexp.MustCompile(`/\*[\s\S]*?\*/`)
51+
)
52+
53+
// Template of support file. A support file contains a map from
54+
// diective to its bitmask definitions, and a MatchFunc for it.
55+
//
56+
//go:embed tmpl/support_file.tmpl
57+
var supportFileTmplStr string
58+
59+
//nolint:gochecknoglobals
60+
var supportFileTmpl = template.Must(template.New("supportFile").
61+
Funcs(template.FuncMap{"Join": strings.Join}).Parse(supportFileTmplStr))
62+
63+
//nolint:gochecknoglobals
64+
var ngxVarNameToGo = map[string]string{
65+
"NGX_MAIL_MAIN_CONF": "ngxMailMainConf",
66+
"NGX_STREAM_MAIN_CONF": "ngxStreamMainConf",
67+
"NGX_CONF_TAKE1": "ngxConfTake1",
68+
"NGX_STREAM_UPS_CONF": "ngxStreamUpsConf",
69+
"NGX_HTTP_LIF_CONF": "ngxHTTPLifConf",
70+
"NGX_CONF_TAKE2": "ngxConfTake2",
71+
"NGX_HTTP_UPS_CONF": "ngxHTTPUpsConf",
72+
"NGX_CONF_TAKE23": "ngxConfTake23",
73+
"NGX_CONF_TAKE12": "ngxConfTake12",
74+
"NGX_HTTP_MAIN_CONF": "ngxHTTPMainConf",
75+
"NGX_HTTP_LMT_CONF": "ngxHTTPLmtConf",
76+
"NGX_CONF_TAKE1234": "ngxConfTake1234",
77+
"NGX_MAIL_SRV_CONF": "ngxMailSrvConf",
78+
"NGX_CONF_FLAG": "ngxConfFlag",
79+
"NGX_HTTP_SRV_CONF": "ngxHTTPSrvConf",
80+
"NGX_CONF_1MORE": "ngxConf1More",
81+
"NGX_ANY_CONF": "ngxAnyConf",
82+
"NGX_CONF_TAKE123": "ngxConfTake123",
83+
"NGX_MAIN_CONF": "ngxMainConf",
84+
"NGX_CONF_NOARGS": "ngxConfNoArgs",
85+
"NGX_CONF_2MORE": "ngxConf2More",
86+
"NGX_CONF_TAKE3": "ngxConfTake3",
87+
"NGX_HTTP_SIF_CONF": "ngxHTTPSifConf",
88+
"NGX_EVENT_CONF": "ngxEventConf",
89+
"NGX_CONF_BLOCK": "ngxConfBlock",
90+
"NGX_HTTP_LOC_CONF": "ngxHTTPLocConf",
91+
"NGX_STREAM_SRV_CONF": "ngxStreamSrvConf",
92+
"NGX_DIRECT_CONF": "ngxDirectConf",
93+
"NGX_CONF_TAKE13": "ngxConfTake13",
94+
"NGX_CONF_ANY": "ngxConfAny",
95+
"NGX_CONF_TAKE4": "ngxConfTake4",
96+
"NGX_CONF_TAKE5": "ngxConfTake5",
97+
"NGX_CONF_TAKE6": "ngxConfTake6",
98+
"NGX_CONF_TAKE7": "ngxConfTake7",
99+
}
100+
101+
//nolint:nonamedreturns
102+
func masksFromFile(path string) (directive2Masks map[string][]mask, err error) {
103+
directive2Masks = make(map[string][]mask, 0)
104+
byteContent, err := os.ReadFile(path)
105+
if err != nil {
106+
return nil, err
107+
}
108+
strContent := string(byteContent)
109+
110+
// Remove comments
111+
strContent = singleLineCommentExtracter.ReplaceAllString(strContent, "")
112+
strContent = multiLineCommentExtracter.ReplaceAllString(strContent, "")
113+
strContent = strings.ReplaceAll(strContent, "\r\n", "")
114+
strContent = strings.ReplaceAll(strContent, "\n", "")
115+
116+
// Extract directives definition code blocks, each code block contains a list of directives definition
117+
blocks := directivesDefineBlockExtracter.FindAllStringSubmatch(strContent, -1)
118+
119+
for _, block := range blocks {
120+
// Extract directives and their attributes in the code block, the first dimension of subBlocks
121+
// is index of directive, the second dimension is index of attributes
122+
subBlocks := singleDirectiveExtracter.FindAllStringSubmatch(block[2], -1)
123+
124+
// Iterate through every directive
125+
for _, attributes := range subBlocks {
126+
// Extract attributes from the directive
127+
directiveName := strings.TrimSpace(attributes[1])
128+
directiveMask := strings.Split(attributes[2], "|")
129+
130+
// Transfer C-style mask to go style
131+
for idx, ngxVarName := range directiveMask {
132+
goVarName, found := ngxVarNameToGo[strings.TrimSpace(ngxVarName)]
133+
if !found {
134+
return nil, fmt.Errorf("parsing directive %s, bitmask %s in source code not found in crossplane", directiveName, ngxVarName)
135+
}
136+
directiveMask[idx] = goVarName
137+
}
138+
139+
directive2Masks[directiveName] = append(directive2Masks[directiveName], directiveMask)
140+
}
141+
}
142+
return directive2Masks, nil
143+
}
144+
145+
//nolint:nonamedreturns
146+
func getMasksFromPath(path string) (directive2Masks map[string][]mask, err error) {
147+
directive2Masks = make(map[string][]mask, 0)
148+
149+
err = filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
150+
if err != nil {
151+
return err
152+
}
153+
154+
// Check if the entry is a C/C++ file
155+
// Some dynamic modules are written in C++, like otel
156+
if d.IsDir() {
157+
return nil
158+
}
159+
160+
if !(strings.HasSuffix(path, ".c") || strings.HasSuffix(path, ".cpp")) {
161+
return nil
162+
}
163+
164+
directive2MasksInFile, err := masksFromFile(path)
165+
if err != nil {
166+
return err
167+
}
168+
169+
for directive, masksInFile := range directive2MasksInFile {
170+
directive2Masks[directive] = append(directive2Masks[directive], masksInFile...)
171+
}
172+
173+
return nil
174+
})
175+
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
if len(directive2Masks) == 0 {
181+
return nil, errors.New("can't find any directives in the directory and subdirectories, please check the path")
182+
}
183+
184+
return directive2Masks, nil
185+
}
186+
187+
func genFromSrcCode(codePath string, mapVariableName string, matchFnName string, writer io.Writer) error {
188+
directive2Masks, err := getMasksFromPath(codePath)
189+
if err != nil {
190+
return err
191+
}
192+
193+
err = supportFileTmpl.Execute(writer, supportFileTmplStruct{
194+
Directive2Masks: directive2Masks,
195+
MapVariableName: mapVariableName,
196+
MatchFnName: matchFnName,
197+
})
198+
if err != nil {
199+
return err
200+
}
201+
202+
return nil
203+
}

0 commit comments

Comments
 (0)