github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/generate.go (about)

     1  package gengo
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/format"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  
    12  	"github.com/ipld/go-ipld-prime/schema"
    13  )
    14  
    15  // Generate takes a typesystem and the adjunct config for codegen,
    16  // and emits generated code in the given path with the given package name.
    17  //
    18  // All of the files produced will match the pattern "ipldsch.*.gen.go".
    19  func Generate(pth string, pkgName string, ts schema.TypeSystem, adjCfg *AdjunctCfg) {
    20  	// Emit fixed bits.
    21  	withFile(filepath.Join(pth, "ipldsch_minima.go"), func(f io.Writer) {
    22  		EmitInternalEnums(pkgName, f)
    23  	})
    24  
    25  	externs, err := getExternTypes(pth)
    26  	if err != nil {
    27  		// Consider warning that duplication may be present due to inability to parse destination.
    28  		externs = make(map[string]struct{})
    29  	}
    30  
    31  	// Local helper function for applying generation logic to each type.
    32  	//  We will end up doing this more than once because in this layout, more than one file contains part of the story for each type.
    33  	applyToEachType := func(fn func(tg TypeGenerator, w io.Writer), f io.Writer) {
    34  		// Sort the type names so we have a determinisic order; this affects output consistency.
    35  		//  Any stable order would do, but we don't presently have one, so a sort is necessary.
    36  		types := ts.GetTypes()
    37  		keys := make(sortableTypeNames, 0, len(types))
    38  		for tn := range types {
    39  			if _, exists := externs[tn]; !exists {
    40  				keys = append(keys, tn)
    41  			}
    42  		}
    43  		sort.Sort(keys)
    44  		for _, tn := range keys {
    45  			switch t2 := types[tn].(type) {
    46  			case *schema.TypeBool:
    47  				fn(NewBoolReprBoolGenerator(pkgName, t2, adjCfg), f)
    48  			case *schema.TypeInt:
    49  				fn(NewIntReprIntGenerator(pkgName, t2, adjCfg), f)
    50  			case *schema.TypeFloat:
    51  				fn(NewFloatReprFloatGenerator(pkgName, t2, adjCfg), f)
    52  			case *schema.TypeString:
    53  				fn(NewStringReprStringGenerator(pkgName, t2, adjCfg), f)
    54  			case *schema.TypeBytes:
    55  				fn(NewBytesReprBytesGenerator(pkgName, t2, adjCfg), f)
    56  			case *schema.TypeLink:
    57  				fn(NewLinkReprLinkGenerator(pkgName, t2, adjCfg), f)
    58  			case *schema.TypeStruct:
    59  				switch t2.RepresentationStrategy().(type) {
    60  				case schema.StructRepresentation_Map:
    61  					fn(NewStructReprMapGenerator(pkgName, t2, adjCfg), f)
    62  				case schema.StructRepresentation_Tuple:
    63  					fn(NewStructReprTupleGenerator(pkgName, t2, adjCfg), f)
    64  				case schema.StructRepresentation_Stringjoin:
    65  					fn(NewStructReprStringjoinGenerator(pkgName, t2, adjCfg), f)
    66  				default:
    67  					panic("unrecognized struct representation strategy")
    68  				}
    69  			case *schema.TypeMap:
    70  				fn(NewMapReprMapGenerator(pkgName, t2, adjCfg), f)
    71  			case *schema.TypeList:
    72  				fn(NewListReprListGenerator(pkgName, t2, adjCfg), f)
    73  			case *schema.TypeUnion:
    74  				switch t2.RepresentationStrategy().(type) {
    75  				case schema.UnionRepresentation_Keyed:
    76  					fn(NewUnionReprKeyedGenerator(pkgName, t2, adjCfg), f)
    77  				case schema.UnionRepresentation_Kinded:
    78  					fn(NewUnionReprKindedGenerator(pkgName, t2, adjCfg), f)
    79  				case schema.UnionRepresentation_Stringprefix:
    80  					fn(NewUnionReprStringprefixGenerator(pkgName, t2, adjCfg), f)
    81  				default:
    82  					panic("unrecognized union representation strategy")
    83  				}
    84  			default:
    85  				panic(fmt.Sprintf("add more type switches here :), failed at type %s", tn))
    86  			}
    87  		}
    88  	}
    89  
    90  	// Emit a file with the type table, and the golang type defns for each type.
    91  	withFile(filepath.Join(pth, "ipldsch_types.go"), func(f io.Writer) {
    92  		// Emit headers, import statements, etc.
    93  		fmt.Fprintf(f, "package %s\n\n", pkgName)
    94  		fmt.Fprintf(f, doNotEditComment+"\n\n")
    95  		fmt.Fprintf(f, "import (\n")
    96  		fmt.Fprintf(f, "\t\"github.com/ipld/go-ipld-prime/datamodel\"\n") // referenced for links
    97  		fmt.Fprintf(f, ")\n")
    98  		fmt.Fprintf(f, "var _ datamodel.Node = nil // suppress errors when this dependency is not referenced\n")
    99  
   100  		// Emit the type table.
   101  		EmitTypeTable(pkgName, ts, adjCfg, f)
   102  
   103  		// Emit the type defns matching the schema types.
   104  		fmt.Fprintf(f, "\n// --- type definitions follow ---\n\n")
   105  		applyToEachType(func(tg TypeGenerator, w io.Writer) {
   106  			tg.EmitNativeType(w)
   107  			fmt.Fprintf(f, "\n")
   108  		}, f)
   109  
   110  	})
   111  
   112  	// Emit a file with all the Node/NodeBuilder/NodeAssembler boilerplate.
   113  	//  Also includes typedefs for representation-level data.
   114  	//  Also includes the MaybeT boilerplate.
   115  	withFile(filepath.Join(pth, "ipldsch_satisfaction.go"), func(f io.Writer) {
   116  		// Emit headers, import statements, etc.
   117  		fmt.Fprintf(f, "package %s\n\n", pkgName)
   118  		fmt.Fprintf(f, doNotEditComment+"\n\n")
   119  		fmt.Fprintf(f, "import (\n")
   120  		fmt.Fprintf(f, "\t\"github.com/ipld/go-ipld-prime/datamodel\"\n")   // referenced everywhere.
   121  		fmt.Fprintf(f, "\t\"github.com/ipld/go-ipld-prime/node/mixins\"\n") // referenced by node implementation guts.
   122  		fmt.Fprintf(f, "\t\"github.com/ipld/go-ipld-prime/schema\"\n")      // referenced by maybes (and surprisingly little else).
   123  		fmt.Fprintf(f, ")\n\n")
   124  
   125  		// For each type, we'll emit... everything except the native type, really.
   126  		applyToEachType(func(tg TypeGenerator, w io.Writer) {
   127  			tg.EmitNativeAccessors(w)
   128  			tg.EmitNativeBuilder(w)
   129  			tg.EmitNativeMaybe(w)
   130  			EmitNode(tg, w)
   131  			tg.EmitTypedNodeMethodType(w)
   132  			tg.EmitTypedNodeMethodRepresentation(w)
   133  
   134  			nrg := tg.GetRepresentationNodeGen()
   135  			EmitNode(nrg, w)
   136  
   137  			fmt.Fprintf(f, "\n")
   138  		}, f)
   139  	})
   140  }
   141  
   142  func withFile(filename string, fn func(io.Writer)) {
   143  	// Don't write directly to the file, as that many write syscalls can be
   144  	// expensive. Moreover, they can have a knock-on effect on daemons
   145  	// watching for file changes. gopls can easily eat CPU for many seconds
   146  	// just handling tens of thousands of file writes, for example.
   147  	//
   148  	// To alleviate both of those problems, write to a buffer first, and
   149  	// then write the resulting bytes to disk in a single go.
   150  	// A buffer is slightly better than bufio.Writer, as it gets us a bit
   151  	// more atomicity via the single write.
   152  	buf := new(bytes.Buffer)
   153  	fn(buf)
   154  
   155  	src := buf.Bytes()
   156  	// Format the source before writing, just like gofmt would.
   157  	// This also prevents us from writing invalid syntax to disk.
   158  	src, err := format.Source(src)
   159  	if err != nil {
   160  		panic(err)
   161  	}
   162  
   163  	if err := os.WriteFile(filename, src, 0666); err != nil {
   164  		panic(err)
   165  	}
   166  }
   167  
   168  type sortableTypeNames []schema.TypeName
   169  
   170  func (a sortableTypeNames) Len() int           { return len(a) }
   171  func (a sortableTypeNames) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   172  func (a sortableTypeNames) Less(i, j int) bool { return a[i] < a[j] }