Skip to content

Instantly share code, notes, and snippets.

@SamWhited
Last active June 27, 2021 11:50
Show Gist options
  • Save SamWhited/488e732e127629212af53016ea7c780f to your computer and use it in GitHub Desktop.
Save SamWhited/488e732e127629212af53016ea7c780f to your computer and use it in GitHub Desktop.
XEP Deps
module mellium.im/xepdeps
go 1.17
// The xepdeps command prints a list of dependencies for the input XEP.
// You can use this file by doing something like "go run xepdeps.go myxep.xml |
// dot -Tpng -omyxep.png".
package main
import (
"encoding/xml"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
)
type xep struct {
XMLName xml.Name `xml:"xep"`
Deps []string `xml:"header>dependencies>spec"`
}
func main() {
logger := log.New(os.Stderr, "", 0)
debug := log.New(io.Discard, "DEBUG ", 0)
flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
var (
xepOnly bool
verbose bool
)
flags.BoolVar(&xepOnly, "xeponly", xepOnly, "hide RFCs from output graph")
flags.BoolVar(&verbose, "v", verbose, "print debug output")
err := flags.Parse(os.Args[1:])
if err != nil {
logger.Fatalf("error parsing flags: %v", err)
}
if len(flags.Args()) < 1 {
logger.Fatal("usage: xepdeps xep.xml")
}
if verbose {
debug.SetOutput(os.Stderr)
}
fmt.Println("digraph {")
_, err = printDeps(flags.Args()[0], xepOnly, debug, nil)
if err != nil {
logger.Fatalf("error parsing XEP: %v", err)
}
fmt.Println("}")
}
func printDeps(filename string, xepOnly bool, debug *log.Logger, done []string) ([]string, error) {
for _, d := range done {
if filename == d {
debug.Printf("skipping already completed path: %v", filename)
return done, nil
}
}
var x xep
err := func() error {
debug.Printf("opening %q…", filename)
fd, err := os.Open(filename)
if err != nil {
return fmt.Errorf("error opening xep: %v", err)
}
/* #nosec */
defer fd.Close()
d := xml.NewDecoder(fd)
d.Strict = false
var start xml.StartElement
for {
tok, err := d.Token()
if err != nil {
return fmt.Errorf("error decoding XEP token: %v", err)
}
var ok bool
if start, ok = tok.(xml.StartElement); ok && start.Name.Local == "xep" {
break
}
}
err = d.DecodeElement(&x, &start)
if err != nil {
return fmt.Errorf("error decoding XEP: %v", err)
}
return nil
}()
if err != nil {
return done, nil
}
if xepOnly {
// Filter out non-XEP deps.
// We do this here instead of in the loop below to ensure that we can know
// there are no deps and add a node with no edges if so.
b := x.Deps[:0]
for _, dep := range x.Deps {
if strings.HasPrefix(dep, "XEP-") {
b = append(b, dep)
continue
}
debug.Printf("skipping non-XEP dep on %q…", dep)
}
x.Deps = b
}
if len(x.Deps) == 0 {
debug.Printf("no deps found.")
fmt.Printf("%q\n", strings.ToUpper(strings.TrimSuffix(filename, ".xml")))
return done, nil
}
for _, dep := range x.Deps {
fmt.Printf("%q -> %q\n", strings.ToUpper(strings.TrimSuffix(filename, ".xml")), normalize(dep))
if strings.HasPrefix(dep, "XEP-") {
done, err = printDeps(toFile(dep), xepOnly, debug, append(done, filename))
if err != nil {
return done, nil
}
}
}
return done, nil
}
func normalize(dep string) string {
// Trim out any " (required)" or " (optional)" or the like after the dep name.
idx := strings.IndexByte(dep, ' ')
if idx == -1 {
idx = len(dep)
}
return dep[:idx]
}
func toFile(dep string) string {
return strings.ToLower(normalize(dep)) + ".xml"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment