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] }