github.com/robotn/xgb@v0.0.0-20190912153532-2cb92d044934/xgbgen/context.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/xml"
     6  	"fmt"
     7  	"log"
     8  	"sort"
     9  )
    10  
    11  // Context represents the protocol we're converting to Go, and a writer
    12  // buffer to write the Go source to.
    13  type Context struct {
    14  	protocol *Protocol
    15  	out      *bytes.Buffer
    16  }
    17  
    18  func newContext() *Context {
    19  	return &Context{
    20  		out: bytes.NewBuffer([]byte{}),
    21  	}
    22  }
    23  
    24  // Putln calls put and adds a new line to the end of 'format'.
    25  func (c *Context) Putln(format string, v ...interface{}) {
    26  	c.Put(format+"\n", v...)
    27  }
    28  
    29  // Put is a short alias to write to 'out'.
    30  func (c *Context) Put(format string, v ...interface{}) {
    31  	_, err := c.out.WriteString(fmt.Sprintf(format, v...))
    32  	if err != nil {
    33  		log.Fatalf("There was an error writing to context buffer: %s", err)
    34  	}
    35  }
    36  
    37  // Morph is the big daddy of them all. It takes in an XML byte slice,
    38  // parse it, transforms the XML types into more usable types,
    39  // and writes Go code to the 'out' buffer.
    40  func (c *Context) Morph(xmlBytes []byte) {
    41  	parsedXml := &XML{}
    42  	err := xml.Unmarshal(xmlBytes, parsedXml)
    43  	if err != nil {
    44  		log.Fatal(err)
    45  	}
    46  
    47  	// Parse all imports
    48  	parsedXml.Imports.Eval()
    49  
    50  	// Translate XML types to nice types
    51  	c.protocol = parsedXml.Translate(nil)
    52  
    53  	c.protocol.AddAlignGaps()
    54  
    55  	// Start with Go header.
    56  	c.Putln("// Package %s is the X client API for the %s extension.",
    57  		c.protocol.PkgName(), c.protocol.ExtXName)
    58  	c.Putln("package %s", c.protocol.PkgName())
    59  	c.Putln("")
    60  	c.Putln("// This file is automatically generated from %s.xml. "+
    61  		"Edit at your peril!", c.protocol.Name)
    62  	c.Putln("")
    63  
    64  	// Write imports. We always need to import at least xgb.
    65  	// We also need to import xproto if it's an extension.
    66  	c.Putln("import (")
    67  	c.Putln("\"github.com/robotn/xgb\"")
    68  	c.Putln("")
    69  	if c.protocol.isExt() {
    70  		c.Putln("\"github.com/robotn/xgb/xproto\"")
    71  	}
    72  
    73  	sort.Sort(Protocols(c.protocol.Imports))
    74  	for _, imp := range c.protocol.Imports {
    75  		// We always import xproto, so skip it if it's explicitly imported
    76  		if imp.Name == "xproto" {
    77  			continue
    78  		}
    79  		c.Putln("\"github.com/robotn/xgb/%s\"", imp.Name)
    80  	}
    81  	c.Putln(")")
    82  	c.Putln("")
    83  
    84  	// If this is an extension, create a function to initialize the extension
    85  	// before it can be used.
    86  	if c.protocol.isExt() {
    87  		xname := c.protocol.ExtXName
    88  
    89  		c.Putln("// Init must be called before using the %s extension.",
    90  			xname)
    91  		c.Putln("func Init(c *xgb.Conn) error {")
    92  		c.Putln("reply, err := xproto.QueryExtension(c, %d, \"%s\").Reply()",
    93  			len(xname), xname)
    94  		c.Putln("switch {")
    95  		c.Putln("case err != nil:")
    96  		c.Putln("return err")
    97  		c.Putln("case !reply.Present:")
    98  		c.Putln("return xgb.Errorf(\"No extension named %s could be found on "+
    99  			"on the server.\")", xname)
   100  		c.Putln("}")
   101  		c.Putln("")
   102  		c.Putln("c.ExtLock.Lock()")
   103  		c.Putln("c.Extensions[\"%s\"] = reply.MajorOpcode", xname)
   104  		c.Putln("c.ExtLock.Unlock()")
   105  		c.Putln("for evNum, fun := range xgb.NewExtEventFuncs[\"%s\"] {",
   106  			xname)
   107  		c.Putln("xgb.NewEventFuncs[int(reply.FirstEvent) + evNum] = fun")
   108  		c.Putln("}")
   109  		c.Putln("for errNum, fun := range xgb.NewExtErrorFuncs[\"%s\"] {",
   110  			xname)
   111  		c.Putln("xgb.NewErrorFuncs[int(reply.FirstError) + errNum] = fun")
   112  		c.Putln("}")
   113  		c.Putln("return nil")
   114  		c.Putln("}")
   115  		c.Putln("")
   116  
   117  		// Make sure newExtEventFuncs["EXT_NAME"] map is initialized.
   118  		// Same deal for newExtErrorFuncs["EXT_NAME"]
   119  		c.Putln("func init() {")
   120  		c.Putln("xgb.NewExtEventFuncs[\"%s\"] = make(map[int]xgb.NewEventFun)",
   121  			xname)
   122  		c.Putln("xgb.NewExtErrorFuncs[\"%s\"] = make(map[int]xgb.NewErrorFun)",
   123  			xname)
   124  		c.Putln("}")
   125  		c.Putln("")
   126  	} else {
   127  		// In the xproto package, we must provide a Setup function that uses
   128  		// SetupBytes in xgb.Conn to return a SetupInfo structure.
   129  		c.Putln("// Setup parses the setup bytes retrieved when")
   130  		c.Putln("// connecting into a SetupInfo struct.")
   131  		c.Putln("func Setup(c *xgb.Conn) *SetupInfo {")
   132  		c.Putln("setup := new(SetupInfo)")
   133  		c.Putln("SetupInfoRead(c.SetupBytes, setup)")
   134  		c.Putln("return setup")
   135  		c.Putln("}")
   136  		c.Putln("")
   137  		c.Putln("// DefaultScreen gets the default screen info from SetupInfo.")
   138  		c.Putln("func (s *SetupInfo) DefaultScreen(c *xgb.Conn) *ScreenInfo {")
   139  		c.Putln("return &s.Roots[c.DefaultScreen]")
   140  		c.Putln("}")
   141  		c.Putln("")
   142  	}
   143  
   144  	// Now write Go source code
   145  	sort.Sort(Types(c.protocol.Types))
   146  	sort.Sort(Requests(c.protocol.Requests))
   147  	for _, typ := range c.protocol.Types {
   148  		typ.Define(c)
   149  	}
   150  	for _, req := range c.protocol.Requests {
   151  		req.Define(c)
   152  	}
   153  }