github.com/lheiskan/zebrapack@v4.1.1-0.20181107023619-e955d028f9bf+incompatible/main.go (about)

     1  // zebrapack is a code generation tool for
     2  // creating methods to serialize and de-serialize
     3  // Go data structures to and from ZebraPack (a
     4  // schema-based serialization format that is derived
     5  // from MessagePack2).
     6  //
     7  // This package is targeted at the `go generate` tool.
     8  // To use it, include the following directive in a
     9  // go source file with types requiring source generation:
    10  //
    11  //     //go:generate zebrapack
    12  //
    13  // The go generate tool should set the proper environment variables for
    14  // the generator to execute without any command-line flags. However, the
    15  // following options are supported, if you need them (See zebrapack -h):
    16  //
    17  //   $ zebrapack -h
    18  //
    19  //   Usage of zebrapack:
    20  //
    21  //  -msgp
    22  //    	generate msgpack2 serializers instead of ZebraPack;
    23  //      for backward compatiblity or serializing the zebra
    24  //      schema itself.
    25  //
    26  //   -fast-strings
    27  //     	for speed when reading a string in a message that won't be
    28  //      reused, this flag means we'll use unsafe to cast the string
    29  //      header and avoid allocation.
    30  //
    31  //   -file go generate
    32  //     	input file (or directory); default is $GOFILE, which
    33  //      is set by the go generate command.
    34  //
    35  //   -genid
    36  //     	generate a fresh random zebraSchemaId64 value to
    37  //      include in your Go source schema
    38  //
    39  //   -io
    40  //     	create Encode and Decode methods (default true)
    41  //
    42  //   -marshal
    43  //     	create Marshal and Unmarshal methods (default true)
    44  //
    45  //  -method-prefix string
    46  //    	(optional) prefix that will be pre-prended to
    47  //      the front of generated method names; useful when
    48  //      you need to avoid namespace collisions, but the
    49  //      generated tests will break/the msgp package
    50  //      interfaces won't be satisfied.
    51  //
    52  //  -no-embedded-schema
    53  //      don't embed the schema in the generated files
    54  //
    55  //  -no-structnames-onwire
    56  //    	don't embed the name of the struct in the
    57  //      serialized zebrapack. Skipping the embedded
    58  //      struct names saves time and space and matches
    59  //      what protocol buffers/thrift/capnproto/msgpack do.
    60  //      You must know the type on the wire you expect;
    61  //      or embed a type tag in one universal wrapper
    62  //      struct. Embedded struct names are a feature
    63  //      of ZebraPack to help with dynamic language
    64  //      bindings.
    65  //
    66  //   -o string
    67  //     	output file (default is {input_file}_gen.go
    68  //
    69  //   -schema-to-go string
    70  //     	(standalone functionality) path to schema in msgpack2
    71  //      format; we will convert it to Go, write the Go on stdout,
    72  //      and exit immediately
    73  //
    74  //   -tests
    75  //     	create tests and benchmarks (default true)
    76  //
    77  //   -unexported
    78  //     	also process unexported types
    79  //
    80  //   -write-schema string
    81  // 		write schema header to this file; - for stdout
    82  //
    83  //
    84  // For more information, please read README.md, and the wiki at github.com/glycerine/zebrapack
    85  //
    86  package main
    87  
    88  import (
    89  	cryptorand "crypto/rand"
    90  	"encoding/binary"
    91  	"flag"
    92  	"fmt"
    93  	"io/ioutil"
    94  	"os"
    95  	"path/filepath"
    96  	"strings"
    97  
    98  	"github.com/glycerine/zebrapack/cfg"
    99  	"github.com/glycerine/zebrapack/gen"
   100  	"github.com/glycerine/zebrapack/parse"
   101  	"github.com/glycerine/zebrapack/printer"
   102  	"github.com/glycerine/zebrapack/zebra"
   103  )
   104  
   105  func main() {
   106  	myflags := flag.NewFlagSet("zebrapack", flag.ExitOnError)
   107  	c := &cfg.ZebraConfig{}
   108  	c.DefineFlags(myflags)
   109  
   110  	err := myflags.Parse(os.Args[1:])
   111  	err = c.ValidateConfig()
   112  	if err != nil {
   113  		fmt.Printf("zebrapack command line flag error: '%s'\n", err)
   114  		os.Exit(1)
   115  	}
   116  
   117  	if c.GenSchemaId {
   118  		var by [8]byte
   119  		cryptorand.Read(by[:])
   120  		n := binary.LittleEndian.Uint64(by[:])
   121  		n &= 0x0001ffffffffffff // restrict to 53 bits so R and js work
   122  		fmt.Printf("\n// This crypto-randomly generated zebraSchemaId64 is a 53-bit\n"+
   123  			"// integer that identifies your namespace.\n"+
   124  			"// Paste it into your Go source.\n"+
   125  			"  const zebraSchemaId64 int64 = 0x%x // %v\n\n", n, n)
   126  		os.Exit(0)
   127  	}
   128  
   129  	if c.SchemaToGo != "" {
   130  		handleSchemaToGo(c)
   131  		os.Exit(0)
   132  	}
   133  
   134  	// GOFILE is set by go generate
   135  	if c.GoFile == "" {
   136  		c.GoFile = os.Getenv("GOFILE")
   137  		if c.GoFile == "" {
   138  			fmt.Println("No file to parse.")
   139  			os.Exit(1)
   140  		}
   141  	}
   142  
   143  	gen.SetFilename(c.GoFile)
   144  	var mode gen.Method
   145  	if c.Encode {
   146  		mode |= (gen.Encode | gen.Decode | gen.Size | gen.FieldsEmpty)
   147  	}
   148  	if c.Marshal {
   149  		mode |= (gen.Marshal | gen.Unmarshal | gen.Size | gen.FieldsEmpty)
   150  	}
   151  	if c.Tests {
   152  		mode |= gen.Test
   153  	}
   154  
   155  	if mode&^gen.Test == 0 {
   156  		fmt.Println("No methods to generate; -io=false && -marshal=false")
   157  		os.Exit(1)
   158  	}
   159  
   160  	if err := Run(mode, c); err != nil {
   161  		fmt.Println(err.Error())
   162  		os.Exit(1)
   163  	}
   164  }
   165  
   166  // Run writes all methods using the associated file or path, e.g.
   167  //
   168  //	err := msgp.Run("path/to/myfile.go", gen.Size|gen.Marshal|gen.Unmarshal|gen.Test, false)
   169  //
   170  func Run(mode gen.Method, c *cfg.ZebraConfig) error {
   171  	if mode&^gen.Test == 0 {
   172  		return nil
   173  	}
   174  	fmt.Println("======== ZebraPack Code Generator  =======")
   175  	fmt.Printf(">>> Input: \"%s\"\n", c.GoFile)
   176  	var fs *parse.FileSet
   177  	var err error
   178  	if c.NoLoad {
   179  		fs, err = parse.FileNoLoad(c)
   180  	} else {
   181  		fs, err = parse.File(c)
   182  	}
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	if len(fs.Identities) == 0 {
   188  		fmt.Println("No types requiring code generation were found!")
   189  		return nil
   190  	}
   191  
   192  	if c.WriteSchema != "" { // saveSchemaAsMsgpackToFile
   193  		err := fs.SaveMsgpackFile(c.GoFile, c.WriteSchema)
   194  		if err != nil {
   195  			panic(err)
   196  		}
   197  	}
   198  
   199  	return printer.PrintFile(newFilename(c.Out, c.GoFile, fs.Package), fs, mode, c, c.GoFile)
   200  }
   201  
   202  // picks a new file name based on input flags and input filename(s).
   203  func newFilename(out, old, pkg string) string {
   204  	if out != "" {
   205  		if pre := strings.TrimPrefix(out, old); len(pre) > 0 &&
   206  			!strings.HasSuffix(out, ".go") {
   207  			return filepath.Join(old, out)
   208  		}
   209  		return out
   210  	}
   211  
   212  	if fi, err := os.Stat(old); err == nil && fi.IsDir() {
   213  		old = filepath.Join(old, pkg)
   214  	}
   215  	// new file name is old file name + _gen.go
   216  	return strings.TrimSuffix(old, ".go") + "_gen.go"
   217  }
   218  
   219  func fileExists(name string) bool {
   220  	fi, err := os.Stat(name)
   221  	if err != nil {
   222  		return false
   223  	}
   224  	if fi.IsDir() {
   225  		return false
   226  	}
   227  	return true
   228  }
   229  
   230  func handleSchemaToGo(c *cfg.ZebraConfig) {
   231  	if !fileExists(c.SchemaToGo) {
   232  		fmt.Fprintf(os.Stderr, "error: -schema-to-go '%s' path not found\n", c.SchemaToGo)
   233  		os.Exit(1)
   234  	}
   235  	by, err := ioutil.ReadFile(c.SchemaToGo)
   236  	if err != nil {
   237  		fmt.Fprintf(os.Stderr, "error: -schema-to-go '%s' produced error on reading file: %v\n",
   238  			c.SchemaToGo, err)
   239  		os.Exit(1)
   240  	}
   241  	var sch zebra.Schema
   242  	_, err = sch.UnmarshalMsg(by)
   243  	if err != nil {
   244  		fmt.Fprintf(os.Stderr, "error: -schema-to-go '%s' produced error on UnmarshalMsg: %v\n",
   245  			c.SchemaToGo, err)
   246  		os.Exit(1)
   247  	}
   248  	err = sch.WriteToGo(os.Stdout, c.SchemaToGo, "main")
   249  	if err != nil {
   250  		fmt.Fprintf(os.Stderr, "error: -schema-to-go '%s' produced error on UnmarshalMsg: %v\n",
   251  			c.SchemaToGo, err)
   252  		os.Exit(1)
   253  	}
   254  }