Use gotasks for specification-generated source code.

This commit is contained in:
John Beisley 2014-06-07 20:07:54 +01:00
parent 65abff0690
commit 9ba1a7725b
3 changed files with 150 additions and 131 deletions

9
README.md Normal file
View File

@ -0,0 +1,9 @@
goupnp is a UPnP client library for Go
Regenerating dcps generated source code:
----------------------------------------
1. Install gotasks: `go get -u github.com/jingweno/gotask`
2. Change to the gotasks directory: `cd gotasks`
3. Download UPnP specification data (if not done already): `wget http://upnp.org/resources/upnpresources.zip`
4. Regenerate source code: `gotask specgen -s upnpresources.zip -o ../dcps`

View File

@ -1,103 +0,0 @@
package main
import (
"text/template"
)
var packageTmpl = template.Must(template.New("package").Parse(`package {{.Name}}
import (
"time"
"github.com/huin/goupnp"
"github.com/huin/goupnp/soap"
)
// Hack to avoid Go complaining if time isn't used.
var _ time.Time
// Device URNs:
const ({{range .DeviceTypes}}
{{.Const}} = "{{.URN}}"{{end}}
)
// Service URNs:
const ({{range .ServiceTypes}}
{{.Const}} = "{{.URN}}"{{end}}
)
{{range .Services}}
{{$srv := .}}
{{$srvIdent := printf "%s%s" .Name .Version}}
// {{$srvIdent}} is a client for UPnP SOAP service with URN "{{.URN}}". See
// goupnp.ServiceClient, which contains RootDevice and Service attributes which
// are provided for informational value.
type {{$srvIdent}} struct {
goupnp.ServiceClient
}
// New{{$srvIdent}}Clients discovers instances of the service on the network,
// and returns clients to any that are found. errors will contain an error for
// any devices that replied but which could not be queried, and err will be set
// if the discovery process failed outright.
//
// This is a typical entry calling point into this package.
func New{{$srvIdent}}Clients() (clients []*{{$srvIdent}}, errors []error, err error) {
var genericClients []goupnp.ServiceClient
if genericClients, errors, err = goupnp.NewServiceClients({{$srv.Const}}); err != nil {
return
}
clients = make([]*{{$srvIdent}}, len(genericClients))
for i := range genericClients {
clients[i] = &{{$srvIdent}}{genericClients[i]}
}
return
}
{{range .SCPD.Actions}}{{/* loops over *SCPDWithURN values */}}
{{$inargs := .InputArguments}}{{$outargs := .OutputArguments}}
// {{if $inargs}}Arguments:{{range $inargs}}{{$argWrap := $srv.WrapArgument .}}
//
// * {{.Name}}: {{$argWrap.Document}}{{end}}{{end}}
//
// {{if $outargs}}Return values:{{range $outargs}}{{$argWrap := $srv.WrapArgument .}}
//
// * {{.Name}}: {{$argWrap.Document}}{{end}}{{end}}
func (client *{{$srvIdent}}) {{.Name}}({{range $inargs}}{{/*
*/}}{{$argWrap := $srv.WrapArgument .}}{{$argWrap.AsParameter}}, {{end}}{{/*
*/}}) ({{range $outargs}}{{/*
*/}}{{$argWrap := $srv.WrapArgument .}}{{$argWrap.AsParameter}}, {{end}} err error) {
// Request structure.
request := {{if $inargs}}&{{template "argstruct" $inargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
// BEGIN Marshal arguments into request.
{{range $inargs}}{{$argWrap := $srv.WrapArgument .}}
if request.{{.Name}}, err = {{$argWrap.Marshal}}; err != nil {
return
}{{end}}
// END Marshal arguments into request.
// Response structure.
response := {{if $outargs}}&{{template "argstruct" $outargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
// Perform the SOAP call.
if err = client.SOAPClient.PerformAction({{$srv.URNParts.Const}}, "{{.Name}}", request, response); err != nil {
return
}
// BEGIN Unmarshal arguments from response.
{{range $outargs}}{{$argWrap := $srv.WrapArgument .}}
if {{.Name}}, err = {{$argWrap.Unmarshal "response"}}; err != nil {
return
}{{end}}
// END Unmarshal arguments from response.
return
}
{{end}}{{/* range .SCPD.Actions */}}
{{end}}{{/* range .Services */}}
{{define "argstruct"}}struct {{"{"}}{{range .}}
{{.Name}} string
{{end}}{{"}"}}{{end}}
`))

View File

@ -1,14 +1,11 @@
// specgen generates Go code from the UPnP specification files. // +build gotask
//
// The specification is available for download from: package gotasks
// http://upnp.org/resources/upnpresources.zip
package main
import ( import (
"archive/zip" "archive/zip"
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"flag"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -18,18 +15,12 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"text/template"
"github.com/huin/goupnp" "github.com/huin/goupnp"
"github.com/huin/goupnp/scpd" "github.com/huin/goupnp/scpd"
"github.com/huin/goutil/codegen" "github.com/huin/goutil/codegen"
) "github.com/jingweno/gotask/tasking"
// flags
var (
specFilename = flag.String("spec", "", "Path to the specification file.")
outDir = flag.String("out-dir", "", "Path to the output directory.")
enableGofmt = flag.Bool("gofmt", true, "Pass the output through gofmt. "+
"Disable if debugging code output problems.")
) )
var ( var (
@ -37,19 +28,39 @@ var (
serviceURNPrefix = "urn:schemas-upnp-org:service:" serviceURNPrefix = "urn:schemas-upnp-org:service:"
) )
func main() { // NAME
flag.Parse() // specgen - generates Go code from the UPnP specification files.
//
if *specFilename == "" { // DESCRIPTION
log.Fatal("--spec is required") // The specification is available for download from:
//
// OPTIONS
// -s, --spec_filename=<upnpresources.zip>
// Path to the specification file, available from http://upnp.org/resources/upnpresources.zip
// -o, --out_dir=<output directory>
// Path to the output directory. This is is where the dcps source files will be placed. Should normally correspond to the directory for github.com/huin/goupnp/dcps
// --nogofmt
// Disable passing the output through gofmt. Do this if debugging code output problems and needing to see the generated code prior to being passed through gofmt.
func TaskSpecgen(t *tasking.T) {
specFilename := t.Flags.String("spec-filename")
if specFilename == "" {
specFilename = t.Flags.String("s")
} }
if *outDir == "" { if specFilename == "" {
log.Fatal("--out-dir is required") t.Fatal("--spec_filename is required")
} }
outDir := t.Flags.String("out-dir")
if outDir == "" {
outDir = t.Flags.String("o")
}
if outDir == "" {
log.Fatal("--out_dir is required")
}
useGofmt := !t.Flags.Bool("nogofmt")
specArchive, err := openZipfile(*specFilename) specArchive, err := openZipfile(specFilename)
if err != nil { if err != nil {
log.Fatalf("Error opening %s: %v", *specFilename) t.Fatalf("Error opening spec file: %v", err)
} }
defer specArchive.Close() defer specArchive.Close()
@ -62,22 +73,24 @@ func main() {
slashIndex := strings.Index(dirName, "/") slashIndex := strings.Index(dirName, "/")
if slashIndex == -1 { if slashIndex == -1 {
// Should not happen. // Should not happen.
log.Printf("Could not find / in %q", dirName) t.Logf("Could not find / in %q", dirName)
return return
} }
dirName = dirName[:slashIndex] dirName = dirName[:slashIndex]
dcp := dcpsCol.dcpsForDir(dirName) dcp := dcpsCol.dcpsForDir(dirName)
if dcp == nil { if dcp == nil {
log.Printf("No alias defined for directory %q: skipping", dirName) t.Logf("No alias defined for directory %q: skipping %s\n", dirName, f.Name)
continue continue
} else {
t.Logf("Alias found for directory %q: processing %s\n", dirName, f.Name)
} }
dcp.processZipFile(f) dcp.processZipFile(f)
} }
for _, dcp := range dcpsCol.dcpsByAlias { for _, dcp := range dcpsCol.dcpsByAlias {
if err := dcp.writePackage(*outDir); err != nil { if err := dcp.writePackage(outDir, useGofmt); err != nil {
log.Printf("Error writing package %q: %v", dcp.Name, err) log.Printf("Error writing package %q: %v", dcp.Name, err)
} }
} }
@ -164,7 +177,7 @@ func (dcp *DCP) processDeviceFile(file *zip.File) {
}) })
} }
func (dcp *DCP) writePackage(outDir string) error { func (dcp *DCP) writePackage(outDir string, useGofmt bool) error {
packageDirname := filepath.Join(outDir, dcp.Name) packageDirname := filepath.Join(outDir, dcp.Name)
err := os.MkdirAll(packageDirname, os.ModePerm) err := os.MkdirAll(packageDirname, os.ModePerm)
if err != nil && !os.IsExist(err) { if err != nil && !os.IsExist(err) {
@ -176,7 +189,7 @@ func (dcp *DCP) writePackage(outDir string) error {
return err return err
} }
var output io.WriteCloser = packageFile var output io.WriteCloser = packageFile
if *enableGofmt { if useGofmt {
if output, err = codegen.NewGofmtWriteCloser(output); err != nil { if output, err = codegen.NewGofmtWriteCloser(output); err != nil {
packageFile.Close() packageFile.Close()
return err return err
@ -401,3 +414,103 @@ func urnPartsFromSCPDFilename(filename string) (*URNParts, error) {
Version: version, Version: version,
}, nil }, nil
} }
var packageTmpl = template.Must(template.New("package").Parse(`package {{.Name}}
// Generated file - do not edit by hand. See README.md
import (
"time"
"github.com/huin/goupnp"
"github.com/huin/goupnp/soap"
)
// Hack to avoid Go complaining if time isn't used.
var _ time.Time
// Device URNs:
const ({{range .DeviceTypes}}
{{.Const}} = "{{.URN}}"{{end}}
)
// Service URNs:
const ({{range .ServiceTypes}}
{{.Const}} = "{{.URN}}"{{end}}
)
{{range .Services}}
{{$srv := .}}
{{$srvIdent := printf "%s%s" .Name .Version}}
// {{$srvIdent}} is a client for UPnP SOAP service with URN "{{.URN}}". See
// goupnp.ServiceClient, which contains RootDevice and Service attributes which
// are provided for informational value.
type {{$srvIdent}} struct {
goupnp.ServiceClient
}
// New{{$srvIdent}}Clients discovers instances of the service on the network,
// and returns clients to any that are found. errors will contain an error for
// any devices that replied but which could not be queried, and err will be set
// if the discovery process failed outright.
//
// This is a typical entry calling point into this package.
func New{{$srvIdent}}Clients() (clients []*{{$srvIdent}}, errors []error, err error) {
var genericClients []goupnp.ServiceClient
if genericClients, errors, err = goupnp.NewServiceClients({{$srv.Const}}); err != nil {
return
}
clients = make([]*{{$srvIdent}}, len(genericClients))
for i := range genericClients {
clients[i] = &{{$srvIdent}}{genericClients[i]}
}
return
}
{{range .SCPD.Actions}}{{/* loops over *SCPDWithURN values */}}
{{$inargs := .InputArguments}}{{$outargs := .OutputArguments}}
// {{if $inargs}}Arguments:{{range $inargs}}{{$argWrap := $srv.WrapArgument .}}
//
// * {{.Name}}: {{$argWrap.Document}}{{end}}{{end}}
//
// {{if $outargs}}Return values:{{range $outargs}}{{$argWrap := $srv.WrapArgument .}}
//
// * {{.Name}}: {{$argWrap.Document}}{{end}}{{end}}
func (client *{{$srvIdent}}) {{.Name}}({{range $inargs}}{{/*
*/}}{{$argWrap := $srv.WrapArgument .}}{{$argWrap.AsParameter}}, {{end}}{{/*
*/}}) ({{range $outargs}}{{/*
*/}}{{$argWrap := $srv.WrapArgument .}}{{$argWrap.AsParameter}}, {{end}} err error) {
// Request structure.
request := {{if $inargs}}&{{template "argstruct" $inargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
// BEGIN Marshal arguments into request.
{{range $inargs}}{{$argWrap := $srv.WrapArgument .}}
if request.{{.Name}}, err = {{$argWrap.Marshal}}; err != nil {
return
}{{end}}
// END Marshal arguments into request.
// Response structure.
response := {{if $outargs}}&{{template "argstruct" $outargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
// Perform the SOAP call.
if err = client.SOAPClient.PerformAction({{$srv.URNParts.Const}}, "{{.Name}}", request, response); err != nil {
return
}
// BEGIN Unmarshal arguments from response.
{{range $outargs}}{{$argWrap := $srv.WrapArgument .}}
if {{.Name}}, err = {{$argWrap.Unmarshal "response"}}; err != nil {
return
}{{end}}
// END Unmarshal arguments from response.
return
}
{{end}}{{/* range .SCPD.Actions */}}
{{end}}{{/* range .Services */}}
{{define "argstruct"}}struct {{"{"}}{{range .}}
{{.Name}} string
{{end}}{{"}"}}{{end}}
`))