Skip to content

Commit 1e8d317

Browse files
authored
feat: add NGINX variable documentation (#203)
* feat: parse XML for module-defined variables * feat: new funcs to see variables New function and new data file from running the converter. * chore: update changelog
1 parent ead4ac8 commit 1e8d317

File tree

11 files changed

+1271
-81
lines changed

11 files changed

+1271
-81
lines changed

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Changelog
22

3-
## 1.0.0 (Month Date, Year)
3+
## 1.0.14 (June 6, 2024)
44

5-
Initial release of the NGINX template repository.
5+
Changed source and version from the Mercurial repository to the GitHub one.
66

7-
## 1.0.14 (June 6, 2024)
7+
## 1.1 (June, 11 2024)
88

9-
Changed source and version from the Mercurial repository to the GitHub one.
9+
Added NGINX variables (e.g. `$binary_remote_addr`) to the dataset.

examples/autocomplete/index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { getDirectives, Format, Directive } from '@nginx/reference-lib'
1+
import {
2+
getDirectives,
3+
Format,
4+
Directive,
5+
getVariables,
6+
} from '@nginx/reference-lib'
27
type autocomplete = {
38
/** name of the NGINX module */
49
m: string
@@ -10,10 +15,10 @@ type autocomplete = {
1015
* nginx config */
1116
v?: string
1217
/** markdown CSV for valid contexts */
13-
c: string
18+
c?: string
1419
/** markdown-formatted syntax specifications, including directive name.
1520
* Multiple syntaxes are seperated by newlines */
16-
s: string
21+
s?: string
1722
}
1823

1924
function toAutocomplete(d: Directive): autocomplete {
@@ -32,5 +37,10 @@ function toAutocomplete(d: Directive): autocomplete {
3237
return ret
3338
}
3439

35-
const formatted = getDirectives(Format.Markdown).map(toAutocomplete)
36-
console.log(JSON.stringify(formatted, undefined, 4))
40+
const directives = getDirectives(Format.Markdown).map(toAutocomplete)
41+
const variables = getVariables(Format.Markdown).map((v) => ({
42+
m: v.module,
43+
n: v.name,
44+
d: v.description,
45+
}))
46+
console.log(JSON.stringify(directives.concat(variables), undefined, 4))

reference-converter/internal/output/output.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@ type Directive struct {
2121
DescriptionHtml string `json:"description_html"`
2222
}
2323

24+
type Variable struct {
25+
Name string `json:"name"`
26+
DescriptionMd string `json:"description_md"`
27+
DescriptionHtml string `json:"description_html"`
28+
}
29+
2430
type Module struct {
2531
Id string `json:"id"`
2632
Name string `json:"name"`
2733
Directives []Directive `json:"directives"`
34+
Variables []Variable `json:"variables,omitempty"`
2835
}
2936

3037
func toModule(m *parse.Module) Module {
@@ -45,6 +52,13 @@ func toModule(m *parse.Module) Module {
4552
DescriptionHtml: directive.Prose.ToHTML(),
4653
})
4754
}
55+
for _, variable := range section.Variables {
56+
module.Variables = append(module.Variables, Variable{
57+
Name: variable.Name,
58+
DescriptionMd: variable.Prose.ToMarkdown(),
59+
DescriptionHtml: variable.Prose.ToHTML(),
60+
})
61+
}
4862
}
4963
return module
5064
}

reference-converter/internal/parse/elements.go

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package parse
33
import (
44
"encoding/xml"
55
"fmt"
6+
"regexp"
7+
"strings"
8+
69
"github.com/gomarkdown/markdown"
710
"github.com/gomarkdown/markdown/html"
811
"github.com/gomarkdown/markdown/parser"
9-
"regexp"
10-
"strings"
1112
)
1213

1314
// Syntax contain the markdown formatted syntax for the directive, very close to
@@ -153,10 +154,100 @@ type Directive struct {
153154
Prose Prose `xml:"para"`
154155
}
155156

157+
// Variable represents an NGINX variable defined by a module, e.g $binary_remote_addr.
158+
type Variable struct {
159+
Name string
160+
Prose Prose
161+
}
162+
163+
// unmarshalVariablesCML extracts NGINX variables from the common pattern:
164+
//
165+
// <section id="variables">
166+
// <list type="tag">
167+
// <tag-name><var>$VARIABLE_NAME</var></tag-name>
168+
// <tag-desc>$DOCUMENTATION</tag-desc>
169+
// <tag-name><var>$VARIABLE_NAME</var><value>$DYNAMIC_SUFFIX</value></tag-name>
170+
// <tag-desc>$DOCUMENTATION</tag-desc>
171+
// </list>
172+
// </section>
173+
func unmarshalVariablesCML(d *xml.Decoder, start xml.StartElement) ([]Variable, error) {
174+
var v struct {
175+
ID string `xml:"id,attr"`
176+
Paragraphs []struct {
177+
List struct {
178+
TagNames []struct {
179+
Name string `xml:"var"`
180+
Suffix string `xml:"value"`
181+
} `xml:"tag-name"`
182+
TagDesc []Prose `xml:"tag-desc"`
183+
} `xml:"list"`
184+
} `xml:"para"`
185+
}
186+
if err := d.DecodeElement(&v, &start); err != nil {
187+
return nil, fmt.Errorf("failed to parse variables: %w", err)
188+
}
189+
var vs []Variable
190+
for _, para := range v.Paragraphs {
191+
if len(para.List.TagDesc) != len(para.List.TagNames) {
192+
return nil, fmt.Errorf(
193+
"invalid variables section, need to have the same number of names (%d) and descriptions (%d)",
194+
len(para.List.TagNames), len(para.List.TagDesc),
195+
)
196+
}
197+
for idx, tn := range para.List.TagNames {
198+
name := tn.Name
199+
if tn.Suffix != "" {
200+
name += strings.ToUpper(tn.Suffix)
201+
}
202+
vs = append(vs, Variable{
203+
Name: name,
204+
Prose: para.List.TagDesc[idx],
205+
})
206+
}
207+
}
208+
209+
return vs, nil
210+
}
211+
156212
type Section struct {
157-
ID string `xml:"id,attr"`
158-
Directives []Directive `xml:"directive"`
159-
Prose Prose `xml:"para"`
213+
ID string
214+
Directives []Directive
215+
Prose Prose
216+
Variables []Variable
217+
}
218+
219+
// UnmarshalXML handles parsing sections with directives vs sections with variables.
220+
func (s *Section) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
221+
attrs := newAttrs(start.Attr)
222+
if attrs["id"] == "variables" {
223+
// parse as a list of variables
224+
vs, err := unmarshalVariablesCML(d, start)
225+
if err != nil {
226+
return fmt.Errorf("failed to unmarshall variables: %w", err)
227+
}
228+
*s = Section{
229+
ID: "variables",
230+
Variables: vs,
231+
}
232+
return nil
233+
}
234+
235+
// parse as a normal section
236+
var sec struct {
237+
ID string `xml:"id,attr"`
238+
Directives []Directive `xml:"directive"`
239+
Prose Prose `xml:"para"`
240+
}
241+
if err := d.DecodeElement(&sec, &start); err != nil {
242+
return err
243+
}
244+
245+
*s = Section{
246+
ID: sec.ID,
247+
Directives: sec.Directives,
248+
Prose: sec.Prose,
249+
}
250+
return nil
160251
}
161252

162253
type Module struct {

reference-converter/internal/parse/parse_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ func TestParse(t *testing.T) {
4141
},
4242
},
4343
},
44+
{
45+
ID: "variables",
46+
Variables: []parse.Variable{
47+
{
48+
Name: "$wildcard_var_NAME",
49+
Prose: parse.Prose{
50+
{Content: "\nI support a dynamic suffix *`name`*\n"},
51+
},
52+
},
53+
{
54+
Name: "$variable",
55+
Prose: parse.Prose{
56+
{Content: "\nI am a variable with `formatting` in my desc\n"},
57+
},
58+
},
59+
},
60+
},
4461
},
4562
},
4663
},

reference-converter/internal/parse/testdata/module.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,21 @@ Can have more than one, with some&nbsp;html&mdash;ish entities and <literal>verb
2727

2828
</directive>
2929
</section>
30+
<section id="variables">
31+
<para>We add these variables:</para>
32+
33+
<para>
34+
<list type="tag">
35+
<tag-name><var>$wildcard_var_</var><value>name</value></tag-name>
36+
<tag-desc>
37+
I support a dynamic suffix <value>name</value>
38+
</tag-desc>
39+
40+
<tag-name><var>$variable</var></tag-name>
41+
<tag-desc>
42+
I am a variable with <literal>formatting</literal> in my desc
43+
</tag-desc>
44+
</list>
45+
</para>
46+
</section>
3047
</module>

reference-lib/index.ts

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import reference from './src/reference.json'
22

33
export interface Directive {
4-
name: string
5-
module: string
6-
description: string
7-
syntax: string[]
8-
contexts: string[]
9-
isBlock: boolean
10-
default: string
4+
name: string
5+
module: string
6+
description: string
7+
syntax: string[]
8+
contexts: string[]
9+
isBlock: boolean
10+
default: string
11+
}
12+
13+
export interface Variable {
14+
name: string
15+
module: string
16+
description: string
1117
}
1218

1319
export enum Format {
@@ -41,37 +47,63 @@ for (const modules of reference.modules) {
4147
* Returns all the nginx directives
4248
*
4349
* @param: format: format of the return type HTML or markdown
44-
*
4550
* @return: an array of Directives
4651
*/
47-
export function getDirectives(format=Format.HTML): Directive[] {
48-
const directives = reference.modules.flatMap((m) =>
49-
m.directives.map((d) => ({...d, module: m.name})))
50-
.map ((d) => ({
51-
name: d.name,
52-
module: d.module,
53-
description: format === Format.HTML ? d.description_html : d.description_md,
54-
syntax: format === Format.HTML ? d.syntax_html : d.syntax_md,
55-
contexts: d.contexts,
56-
isBlock: d.isBlock,
57-
default: d.default
58-
} as Directive))
59-
return directives
52+
export function getDirectives(format = Format.HTML): Directive[] {
53+
const directives = reference.modules
54+
.flatMap((m) => m.directives.map((d) => ({ ...d, module: m.name })))
55+
.map(
56+
(d) =>
57+
({
58+
name: d.name,
59+
module: d.module,
60+
description:
61+
format === Format.HTML ? d.description_html : d.description_md,
62+
syntax: format === Format.HTML ? d.syntax_html : d.syntax_md,
63+
contexts: d.contexts,
64+
isBlock: d.isBlock,
65+
default: d.default,
66+
} as Directive)
67+
)
68+
return directives
69+
}
70+
71+
/**
72+
* Returns all variables defined by any moduled
73+
*
74+
* @param: format: format of the return type HTML or markdown
75+
* @return: an array of Variables
76+
*/
77+
export function getVariables(format = Format.HTML): Variable[] {
78+
return reference.modules.flatMap(
79+
(m) =>
80+
m.variables?.map((v) => ({
81+
name: v.name,
82+
description:
83+
format === Format.HTML ? v.description_html : v.description_md,
84+
module: m.name,
85+
})) ?? []
86+
)
6087
}
6188

6289
/**
63-
* Returns the description corresponding to the directive name
90+
* Returns the description corresponding to the directive or variable name
6491
*
6592
* @param: directive: directive name to find
6693
* @param: module: optional name of module
6794
* @param: format: format of the return type HTML or markdown
6895
*
6996
* @return: a string containing the description of the directive in xml or markdown format
7097
*/
71-
export function find(directive: string, module: string | undefined, format=Format.HTML): string | undefined {
72-
const data =
73-
module
74-
? refDirectives.get(directive)?.find((d) => d.module.toUpperCase() === module.toUpperCase())
75-
: refDirectives.get(directive)?.at(0)
76-
return (format === Format.HTML ? data?.description_html : data?.description_md)
98+
export function find(
99+
directive: string,
100+
module: string | undefined,
101+
format = Format.HTML
102+
): string | undefined {
103+
const data = module
104+
? refDirectives
105+
.get(directive)
106+
?.find((d) => d.module.toUpperCase() === module.toUpperCase())
107+
: refDirectives.get(directive)?.at(0)
108+
return format === Format.HTML ? data?.description_html : data?.description_md
77109
}

reference-lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nginx/reference-lib",
3-
"version": "1.0.14",
3+
"version": "1.1.0",
44
"description": "",
55
"main": "dist/index.js",
66
"type": "module",

0 commit comments

Comments
 (0)