Skip to content

Commit

Permalink
feat: parse XML for module-defined variables
Browse files Browse the repository at this point in the history
  • Loading branch information
ryepup committed Jun 7, 2024
1 parent ead4ac8 commit e17e6d1
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 5 deletions.
14 changes: 14 additions & 0 deletions reference-converter/internal/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ type Directive struct {
DescriptionHtml string `json:"description_html"`
}

type Variable struct {
Name string `json:"name"`
DescriptionMd string `json:"description_md"`
DescriptionHtml string `json:"description_html"`
}

type Module struct {
Id string `json:"id"`
Name string `json:"name"`
Directives []Directive `json:"directives"`
Variables []Variable `json:"variables,omitempty"`
}

func toModule(m *parse.Module) Module {
Expand All @@ -45,6 +52,13 @@ func toModule(m *parse.Module) Module {
DescriptionHtml: directive.Prose.ToHTML(),
})
}
for _, variable := range section.Variables {
module.Variables = append(module.Variables, Variable{
Name: variable.Name,
DescriptionMd: variable.Prose.ToMarkdown(),
DescriptionHtml: variable.Prose.ToHTML(),
})
}
}
return module
}
Expand Down
101 changes: 96 additions & 5 deletions reference-converter/internal/parse/elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package parse
import (
"encoding/xml"
"fmt"
"regexp"
"strings"

"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"regexp"
"strings"
)

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

// Variable represents an NGINX variable defined by a module, e.g $binary_remote_addr.
type Variable struct {
Name string
Prose Prose
}

// unmarshalVariablesCML extracts NGINX variables from the common pattern:
//
// <section id="variables">
// <list type="tag">
// <tag-name><var>$VARIABLE_NAME</var></tag-name>
// <tag-desc>$DOCUMENTATION</tag-desc>
// <tag-name><var>$VARIABLE_NAME</var><value>$DYNAMIC_SUFFIX</value></tag-name>
// <tag-desc>$DOCUMENTATION</tag-desc>
// </list>
// </section>
func unmarshalVariablesCML(d *xml.Decoder, start xml.StartElement) ([]Variable, error) {
var v struct {
ID string `xml:"id,attr"`
Paragraphs []struct {
List struct {
TagNames []struct {
Name string `xml:"var"`
Suffix string `xml:"value"`
} `xml:"tag-name"`
TagDesc []Prose `xml:"tag-desc"`
} `xml:"list"`
} `xml:"para"`
}
if err := d.DecodeElement(&v, &start); err != nil {
return nil, fmt.Errorf("failed to parse variables: %w", err)
}
var vs []Variable
for _, para := range v.Paragraphs {
if len(para.List.TagDesc) != len(para.List.TagNames) {
return nil, fmt.Errorf(
"invalid variables section, need to have the same number of names (%d) and descriptions (%d)",
len(para.List.TagNames), len(para.List.TagDesc),
)
}
for idx, tn := range para.List.TagNames {
name := tn.Name
if tn.Suffix != "" {
name += strings.ToUpper(tn.Suffix)
}
vs = append(vs, Variable{
Name: name,
Prose: para.List.TagDesc[idx],
})
}
}

return vs, nil
}

type Section struct {
ID string `xml:"id,attr"`
Directives []Directive `xml:"directive"`
Prose Prose `xml:"para"`
ID string
Directives []Directive
Prose Prose
Variables []Variable
}

// UnmarshalXML handles parsing sections with directives vs sections with variables.
func (s *Section) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
attrs := newAttrs(start.Attr)
if attrs["id"] == "variables" {
// parse as a list of variables
vs, err := unmarshalVariablesCML(d, start)
if err != nil {
return fmt.Errorf("failed to unmarshall variables: %w", err)
}
*s = Section{
ID: "variables",
Variables: vs,
}
return nil
}

// parse as a normal section
var sec struct {
ID string `xml:"id,attr"`
Directives []Directive `xml:"directive"`
Prose Prose `xml:"para"`
}
if err := d.DecodeElement(&sec, &start); err != nil {
return err
}

*s = Section{
ID: sec.ID,
Directives: sec.Directives,
Prose: sec.Prose,
}
return nil
}

type Module struct {
Expand Down
17 changes: 17 additions & 0 deletions reference-converter/internal/parse/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ func TestParse(t *testing.T) {
},
},
},
{
ID: "variables",
Variables: []parse.Variable{
{
Name: "$wildcard_var_NAME",
Prose: parse.Prose{
{Content: "\nI support a dynamic suffix *`name`*\n"},
},
},
{
Name: "$variable",
Prose: parse.Prose{
{Content: "\nI am a variable with `formatting` in my desc\n"},
},
},
},
},
},
},
},
Expand Down
17 changes: 17 additions & 0 deletions reference-converter/internal/parse/testdata/module.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,21 @@ Can have more than one, with some&nbsp;html&mdash;ish entities and <literal>verb

</directive>
</section>
<section id="variables">
<para>We add these variables:</para>

<para>
<list type="tag">
<tag-name><var>$wildcard_var_</var><value>name</value></tag-name>
<tag-desc>
I support a dynamic suffix <value>name</value>
</tag-desc>

<tag-name><var>$variable</var></tag-name>
<tag-desc>
I am a variable with <literal>formatting</literal> in my desc
</tag-desc>
</list>
</para>
</section>
</module>

0 comments on commit e17e6d1

Please sign in to comment.