goupnp/v2alpha/cmd/goupnp2srvgen/main.go

294 lines
7.1 KiB
Go
Raw Normal View History

package main
import (
"encoding/xml"
"errors"
"flag"
"fmt"
"os"
2022-06-06 17:00:39 +00:00
"path/filepath"
"reflect"
2022-06-06 17:00:39 +00:00
"sort"
"strconv"
"strings"
2022-06-06 17:00:39 +00:00
"text/template"
2022-06-06 17:00:39 +00:00
"github.com/huin/goupnp/v2alpha/cmd/goupnp2srvgen/tmplfuncs"
2022-05-27 06:10:15 +00:00
"github.com/huin/goupnp/v2alpha/cmd/goupnp2srvgen/zipread"
"github.com/huin/goupnp/v2alpha/description/srvdesc"
2022-06-06 17:00:39 +00:00
"github.com/huin/goupnp/v2alpha/description/typedesc"
"github.com/huin/goupnp/v2alpha/description/xmlsrvdesc"
"github.com/huin/goupnp/v2alpha/soap"
2022-06-06 17:00:39 +00:00
"github.com/huin/goupnp/v2alpha/soap/types"
)
var (
2022-06-06 17:00:39 +00:00
srvTemplate = flag.String("srv_template", "", "Path to srv.gotemplate.")
upnpresourcesZip = flag.String("upnpresources_zip", "", "Path to upnpresources.zip.")
)
const soapActionInterface = "SOAPActionInterface"
func main() {
flag.Parse()
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
func run() error {
if len(flag.Args()) > 0 {
return fmt.Errorf("unused arguments: %s", strings.Join(flag.Args(), " "))
}
2022-06-06 17:00:39 +00:00
if *srvTemplate == "" {
return errors.New("-srv_template is a required flag.")
}
tmpl, err := template.New(filepath.Base(*srvTemplate)).Funcs(template.FuncMap{
"args": tmplfuncs.Args,
"quote": strconv.Quote,
}).ParseFiles(*srvTemplate)
if err != nil {
return fmt.Errorf("loading srv_template %q: %w", *srvTemplate, err)
}
if *upnpresourcesZip == "" {
return errors.New("-upnpresources_zip is a required flag.")
}
f, err := os.Open(*upnpresourcesZip)
if err != nil {
return err
}
defer f.Close()
upnpresources, err := zipread.FromOsFile(f)
if err != nil {
return err
}
2022-06-06 17:00:39 +00:00
// Use default type map for now. Addtional types could be use instead or
// as well as necessary for extended types.
typeMap := types.TypeMap().Clone()
typeMap[soapActionInterface] = typedesc.TypeDesc{
GoType: reflect.TypeOf((*soap.SOAPAction)(nil)).Elem(),
}
2022-06-06 17:00:39 +00:00
for _, m := range manifests {
2022-06-06 17:00:39 +00:00
if err := processDCP(upnpresources, m, typeMap, tmpl); err != nil {
return fmt.Errorf("processing DCP %s: %w", m.Path, err)
}
}
return nil
}
var manifests = []*DCPSpecManifest{
{
Path: "standardizeddcps/Internet Gateway_2/UPnP-gw-IGD-TestFiles-20101210.zip",
2022-06-06 17:00:39 +00:00
Services: []*ServiceManifest{
{
Package: "lanhostconfigmanagement1",
ServiceType: "urn:schemas-upnp-org:service:LANHostConfigManagement:1",
Path: "xml data files/service/LANHostConfigManagement1.xml",
2022-06-06 17:00:39 +00:00
},
{
Package: "wanpppconnection1",
ServiceType: "urn:schemas-upnp-org:service:WANPPPConnection:1",
Path: "xml data files/service/WANPPPConnection1.xml",
2022-06-06 17:00:39 +00:00
},
},
},
}
func processDCP(
upnpresources *zipread.ZipRead,
manifest *DCPSpecManifest,
2022-06-06 17:00:39 +00:00
typeMap typedesc.TypeMap,
tmpl *template.Template,
) error {
dcpSpecData, err := upnpresources.OpenZip(manifest.Path)
if err != nil {
return err
}
2022-06-06 17:00:39 +00:00
for _, srvManifest := range manifest.Services {
if err := processService(dcpSpecData, srvManifest, typeMap, tmpl); err != nil {
return fmt.Errorf("processing service %s: %w", srvManifest.ServiceType, err)
}
}
return nil
}
func processService(
dcpSpecData *zipread.ZipRead,
2022-06-06 17:00:39 +00:00
srvManifest *ServiceManifest,
typeMap typedesc.TypeMap,
tmpl *template.Template,
) error {
2022-06-06 17:00:39 +00:00
f, err := dcpSpecData.Open(srvManifest.Path)
if err != nil {
return err
}
defer f.Close()
d := xml.NewDecoder(f)
xmlSCPD := &xmlsrvdesc.SCPD{}
if err := d.Decode(xmlSCPD); err != nil {
return err
}
xmlSCPD.Clean()
2022-06-06 17:00:39 +00:00
sd, err := srvdesc.FromXML(xmlSCPD)
if err != nil {
return fmt.Errorf("transforming service description: %w", err)
}
imps, err := accumulateImports(sd, typeMap)
if err != nil {
return err
}
2022-06-06 17:00:39 +00:00
err = tmpl.ExecuteTemplate(os.Stdout, "service", tmplArgs{
Manifest: srvManifest,
Imps: imps,
SCPD: sd,
2022-06-06 17:00:39 +00:00
})
if err != nil {
return fmt.Errorf("executing srv_template: %w", err)
}
return nil
}
type DCPSpecManifest struct {
// Path is the file path within upnpresources.zip to the DCP spec ZIP file.
Path string
// Services maps from a service name (e.g. "FooBar:1") to a path within the DCP spec ZIP file
// (e.g. "xml data files/service/FooBar1.xml").
2022-06-06 17:00:39 +00:00
Services []*ServiceManifest
}
type ServiceManifest struct {
// Package is the Go package name to generate e.g. "foo1".
Package string
// ServiceType is the SOAP namespace and service type that identifes the service e.g.
2022-06-06 17:00:39 +00:00
// "urn:schemas-upnp-org:service:Foo:1"
ServiceType string
2022-06-06 17:00:39 +00:00
// Path within the DCP spec ZIP file e.g. "xml data files/service/Foo1.xml".
Path string
}
type tmplArgs struct {
Manifest *ServiceManifest
Imps *imports
SCPD *srvdesc.SCPD
2022-06-06 17:00:39 +00:00
}
type imports struct {
// Maps from a type name like "ui4" to the `alias.name` for the import.
TypeRefByTypeName map[string]string
// Each required import line, ordered by path.
ImportLines []importItem
}
type importItem struct {
Alias string
Path string
}
func accumulateImports(srvDesc *srvdesc.SCPD, typeMap typedesc.TypeMap) (*imports, error) {
typeNames := make(map[string]bool)
typeNames[soapActionInterface] = true
2022-06-06 17:00:39 +00:00
err := visitTypesSCPD(srvDesc, func(typeName string) {
typeNames[typeName] = true
})
if err != nil {
return nil, err
}
// Have sorted list of import package paths. Partly for aesthetics of generated code, but also
// to have stable-generated aliases.
paths := make(map[string]bool)
for typeName := range typeNames {
t, ok := typeMap[typeName]
if !ok {
return nil, fmt.Errorf("unknown type %q", typeName)
}
pkgPath := t.GoType.PkgPath()
if pkgPath == "" {
// Builtin type, ignore.
continue
}
paths[pkgPath] = true
}
sortedPaths := make([]string, 0, len(paths))
for path := range paths {
sortedPaths = append(sortedPaths, path)
}
sort.Strings(sortedPaths)
// Generate import aliases.
index := 1
aliasByPath := make(map[string]string, len(paths))
importLines := make([]importItem, 0, len(paths))
for _, path := range sortedPaths {
alias := fmt.Sprintf("pkg%d", index)
index++
importLines = append(importLines, importItem{
Alias: alias,
Path: path,
})
aliasByPath[path] = alias
}
// Populate typeRefByTypeName.
typeRefByTypeName := make(map[string]string, len(typeNames))
for typeName := range typeNames {
goType := typeMap[typeName]
pkgPath := goType.GoType.PkgPath()
alias := aliasByPath[pkgPath]
if alias == "" {
// Builtin type.
typeRefByTypeName[typeName] = goType.GoType.Name()
} else {
typeRefByTypeName[typeName] = fmt.Sprintf(
"%s.%s", alias, goType.GoType.Name())
}
}
return &imports{
TypeRefByTypeName: typeRefByTypeName,
ImportLines: importLines,
}, nil
}
type typeVisitor func(typeName string)
// visitTypesSCPD calls `visitor` with each data type name (e.g. "ui4") referenced
// by action arguments.`
func visitTypesSCPD(scpd *srvdesc.SCPD, visitor typeVisitor) error {
for _, action := range scpd.ActionByName {
if err := visitTypesAction(action, visitor); err != nil {
return err
}
}
return nil
}
func visitTypesAction(action *srvdesc.Action, visitor typeVisitor) error {
for _, arg := range action.InArgs {
sv, err := arg.RelatedStateVariable()
if err != nil {
return err
}
visitor(sv.DataType)
}
for _, arg := range action.OutArgs {
sv, err := arg.RelatedStateVariable()
if err != nil {
return err
}
visitor(sv.DataType)
}
return nil
}