this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

progress on lexgen refactor

+759 -17
-14
cmd/lexgen/codegen.go
··· 1 - package main 2 - 3 - import ( 4 - "io" 5 - ) 6 - 7 - type GenConfig struct { 8 - } 9 - 10 - type Generator struct { 11 - Config *GenConfig 12 - Lex *FlatLexicon 13 - Out io.Writer 14 - }
+177 -3
cmd/lexgen/flatschema.go
··· 1 1 package main 2 2 3 3 import ( 4 + "fmt" 5 + "log/slog" 6 + "slices" 7 + "sort" 8 + "strings" 9 + 4 10 "github.com/bluesky-social/indigo/atproto/lexicon" 5 11 "github.com/bluesky-social/indigo/atproto/syntax" 6 12 ) ··· 8 14 // Intermediate representation of a complete lexicon schema file, containing one or more definitions. 9 15 type FlatLexicon struct { 10 16 NSID syntax.NSID 17 + Description *string 11 18 ExternalRefs map[string]bool // NSID with optional ref 12 19 SelfRefs map[string]bool // def names 13 20 Defs map[string]FlatDef ··· 30 37 } 31 38 32 39 func FlattenSchemaFile(sf *lexicon.SchemaFile) (*FlatLexicon, error) { 33 - // XXX 40 + nsid, err := syntax.ParseNSID(sf.ID) 41 + if err != nil { 42 + return nil, err 43 + } 44 + 45 + fl := FlatLexicon{ 46 + NSID: nsid, 47 + Description: sf.Description, 48 + ExternalRefs: map[string]bool{}, 49 + SelfRefs: map[string]bool{}, 50 + Defs: map[string]FlatDef{}, 51 + Types: []FlatType{}, 52 + } 53 + 54 + // iterate defs in sorted order; except "main" is always first if present 55 + defNames := []string{} 56 + hasMain := false 57 + for name := range sf.Defs { 58 + if name == "main" { 59 + hasMain = true 60 + continue 61 + } 62 + defNames = append(defNames, name) 63 + } 64 + sort.Strings(defNames) 65 + if hasMain { 66 + defNames = append([]string{"main"}, defNames...) 67 + } 68 + 69 + for _, name := range defNames { 70 + def := sf.Defs[name] 71 + if err := fl.flattenDef(name, &def); err != nil { 72 + return nil, err 73 + } 74 + } 75 + 76 + return &fl, nil 34 77 } 35 78 36 79 func (fl *FlatLexicon) flattenDef(name string, def *lexicon.SchemaDef) error { 37 - // XXX 80 + 81 + t, err := defType(def) 82 + if err != nil { 83 + return err 84 + } 85 + 86 + fd := FlatDef{ 87 + Name: name, 88 + Type: t, 89 + } 90 + fl.Defs[name] = fd 91 + 92 + return fl.flattenType(&fd, []string{}, def) 38 93 } 39 94 40 95 func (fl *FlatLexicon) flattenType(fd *FlatDef, tpath []string, def *lexicon.SchemaDef) error { 41 - // XXX 96 + 97 + t, err := defType(def) 98 + if err != nil { 99 + return err 100 + } 101 + 102 + ft := FlatType{ 103 + DefName: fd.Name, 104 + Path: slices.Clone(tpath), 105 + Type: t, 106 + Schema: def, 107 + } 108 + 109 + // XXX: recurse 110 + switch v := def.Inner.(type) { 111 + case lexicon.SchemaRecord: 112 + if err := fl.flattenObject(fd, tpath, &v.Record); err != nil { 113 + return err 114 + } 115 + case lexicon.SchemaQuery: 116 + // v.Properties: only boolean, integer, string, or unknown are allowed, so recursion not really needed? 117 + if v.Output != nil && v.Output.Schema != nil { 118 + tp := slices.Clone(tpath) 119 + tp = append(tp, "output") 120 + if err := fl.flattenType(fd, tp, v.Output.Schema); err != nil { 121 + return err 122 + } 123 + } 124 + case lexicon.SchemaProcedure: 125 + // XXX 126 + /* 127 + if v.Output != nil && v.Output.Schema != nil { 128 + tp := slices.Clone(tpath) 129 + tp = append(tp, "output") 130 + if err := fl.flattenType(fd, tp, v.Output.Schema); err != nil { 131 + return err 132 + } 133 + } 134 + if v.Input != nil { 135 + props = v.Input 136 + } 137 + if v.Output != nil { 138 + props = v.Output 139 + } 140 + */ 141 + case lexicon.SchemaSubscription: 142 + // XXX: 143 + /* 144 + if v.Message != nil { 145 + props = v.Message 146 + } 147 + */ 148 + case lexicon.SchemaArray: 149 + // XXX: isCompoundDef 150 + case lexicon.SchemaObject: 151 + if err := fl.flattenObject(fd, tpath, &v); err != nil { 152 + return err 153 + } 154 + case lexicon.SchemaRef: 155 + if !strings.HasPrefix(v.Ref, "#") { 156 + fl.ExternalRefs[v.Ref] = true 157 + } 158 + case lexicon.SchemaUnion: 159 + for _, ref := range v.Refs { 160 + if !strings.HasPrefix(ref, "#") { 161 + fl.ExternalRefs[ref] = true 162 + } 163 + } 164 + case lexicon.SchemaString, lexicon.SchemaNull, lexicon.SchemaInteger, lexicon.SchemaBoolean, lexicon.SchemaUnknown: 165 + // pass, don't emit (?) 166 + return nil 167 + case lexicon.SchemaToken: 168 + // pass 169 + default: 170 + return fmt.Errorf("unsupported def type for flattening (%s): %T", fd.Name, def.Inner) 171 + } 172 + 173 + fl.Types = append(fl.Types, ft) 174 + return nil 175 + } 176 + 177 + func (fl *FlatLexicon) flattenObject(fd *FlatDef, tpath []string, obj *lexicon.SchemaObject) error { 178 + for fname, field := range obj.Properties { 179 + tp := slices.Clone(tpath) 180 + tp = append(tp, fname) 181 + switch v := field.Inner.(type) { 182 + case lexicon.SchemaNull, lexicon.SchemaBoolean, lexicon.SchemaInteger, lexicon.SchemaString, lexicon.SchemaBytes: 183 + // no-op 184 + case lexicon.SchemaCIDLink, lexicon.SchemaBlob, lexicon.SchemaUnknown: 185 + // no-op, but maybe set a flag on def? 186 + case lexicon.SchemaArray: 187 + if err := fl.flattenType(fd, tp, &v.Items); err != nil { 188 + return err 189 + } 190 + case lexicon.SchemaObject: 191 + if err := fl.flattenType(fd, tp, &field); err != nil { 192 + return err 193 + } 194 + case lexicon.SchemaRef: 195 + slog.Info("reference", "ref", v.Ref) 196 + if strings.HasPrefix(v.Ref, "#") { 197 + fl.SelfRefs[v.Ref] = true 198 + } else { 199 + // TODO: normalize #main refs? 200 + fl.ExternalRefs[v.Ref] = true 201 + } 202 + case lexicon.SchemaUnion: 203 + for _, ref := range v.Refs { 204 + if strings.HasPrefix(ref, "#") { 205 + fl.SelfRefs[ref] = true 206 + } else { 207 + // TODO: normalize #main refs? 208 + fl.ExternalRefs[ref] = true 209 + } 210 + } 211 + default: 212 + return fmt.Errorf("unsupported field type for object flattening: %T", field.Inner) 213 + } 214 + } 215 + return nil 42 216 }
+412
cmd/lexgen/generate.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "io" 6 + "sort" 7 + "strings" 8 + 9 + "github.com/bluesky-social/indigo/atproto/lexicon" 10 + "github.com/bluesky-social/indigo/atproto/syntax" 11 + ) 12 + 13 + type GenConfig struct { 14 + RegisterLexiconTypeID bool 15 + PackageMappings map[string]string 16 + // one of: "type-decoder", "map-string-any", "json-raw-message" 17 + UnknownType string 18 + } 19 + 20 + type FlatGenerator struct { 21 + Config *GenConfig 22 + Lex *FlatLexicon 23 + Out io.Writer 24 + } 25 + 26 + func (gen *FlatGenerator) WriteLexicon() error { 27 + 28 + fmt.Fprintf(gen.Out, "// Auto-generated from Lexicon. DO NOT EDIT BY HAND.\n\n") 29 + fmt.Fprintf(gen.Out, "package %s\n\n", gen.pkgName()) 30 + fmt.Fprintf(gen.Out, "// schema: %s\n\n", gen.Lex.NSID) 31 + fmt.Fprintln(gen.Out, "import (") 32 + for dep, _ := range gen.deps() { 33 + fmt.Fprintf(gen.Out, " %s\n", dep) 34 + } 35 + fmt.Fprint(gen.Out, ")\n\n") 36 + 37 + for _, ft := range gen.Lex.Types { 38 + if err := gen.WriteType(&ft); err != nil { 39 + return err 40 + } 41 + } 42 + return nil 43 + } 44 + 45 + func (gen *FlatGenerator) pkgName() string { 46 + return nsidPkgName(gen.Lex.NSID) 47 + } 48 + 49 + func (gen *FlatGenerator) baseName() string { 50 + // TODO: memoize? 51 + return nsidBaseName(gen.Lex.NSID) 52 + } 53 + 54 + func (gen *FlatGenerator) deps() map[string]bool { 55 + d := map[string]bool{ 56 + "lexutil \"github.com/bluesky-social/indigo/lex/util\"": true, 57 + } 58 + for _, t := range gen.Lex.Types { 59 + switch t.Type { 60 + case "query", "procedure": 61 + d["\"context\""] = true 62 + } 63 + } 64 + for ext, _ := range gen.Lex.ExternalRefs { 65 + if strings.HasPrefix(ext, "com.atproto.") { 66 + d["comatproto \"github.com/bluesky-social/indigo/api/atproto\""] = true 67 + } else { 68 + // XXX: handle more mappings; or log/error if missing 69 + } 70 + } 71 + return d 72 + } 73 + 74 + func (gen *FlatGenerator) WriteType(ft *FlatType) error { 75 + 76 + switch v := ft.Schema.Inner.(type) { 77 + // TODO: case lexicon.SchemaSubscription: 78 + case lexicon.SchemaRecord: 79 + if gen.Config.RegisterLexiconTypeID { 80 + fmt.Fprintf(gen.Out, "func init() {\n") 81 + fmt.Fprintf(gen.Out, " lexutil.RegisterType(\"%s#main\", &%s{})", gen.Lex.NSID, gen.baseName()) 82 + fmt.Fprintf(gen.Out, "}\n\n") 83 + } 84 + // HACK: insert record-level description in to object if nil 85 + if v.Description != nil && v.Record.Description == nil { 86 + v.Record.Description = v.Description 87 + } 88 + if err := gen.writeStruct(ft, &v.Record); err != nil { 89 + return err 90 + } 91 + case lexicon.SchemaQuery: 92 + return gen.writeQuery(ft, &v) 93 + // TODO: method 94 + case lexicon.SchemaProcedure: 95 + // TODO: method 96 + case lexicon.SchemaToken: 97 + // TODO: pass for now; could be a var/const? 98 + case lexicon.SchemaString, lexicon.SchemaInteger, lexicon.SchemaBoolean, lexicon.SchemaUnknown: 99 + // skip 100 + case lexicon.SchemaObject: 101 + if err := gen.writeStruct(ft, &v); err != nil { 102 + return err 103 + } 104 + case lexicon.SchemaUnion: 105 + // TODO: enum 106 + case lexicon.SchemaRef: 107 + // TODO: pass for now. could be an alias type? 108 + default: 109 + return fmt.Errorf("unhandled schema type for codegen: %T", ft.Schema.Inner) 110 + } 111 + 112 + return nil 113 + } 114 + 115 + func isRequired(required []string, fname string) bool { 116 + for _, k := range required { 117 + if k == fname { 118 + return true 119 + } 120 + } 121 + return false 122 + } 123 + 124 + func (gen *FlatGenerator) fieldType(def *lexicon.SchemaDef, optional bool) (string, error) { 125 + // XXX more completeness here 126 + switch v := def.Inner.(type) { 127 + case lexicon.SchemaNull: 128 + return "nil", nil 129 + case lexicon.SchemaBoolean: 130 + if optional { 131 + return "*bool", nil 132 + } else { 133 + return "bool", nil 134 + } 135 + case lexicon.SchemaInteger: 136 + if optional { 137 + return "*int", nil 138 + } else { 139 + return "int", nil 140 + } 141 + case lexicon.SchemaString: 142 + if optional { 143 + return "*string", nil 144 + } else { 145 + return "string", nil 146 + } 147 + case lexicon.SchemaBytes: 148 + if optional { 149 + return "*[]byte", nil 150 + } else { 151 + return "[]byte", nil 152 + } 153 + case lexicon.SchemaCIDLink: 154 + if optional { 155 + return "*cid.Cid", nil 156 + } else { 157 + return "cid.Cid", nil 158 + } 159 + case lexicon.SchemaArray: 160 + t, err := gen.fieldType(&v.Items, false) 161 + if err != nil { 162 + return "", err 163 + } 164 + if optional { 165 + return "*[]" + t, nil 166 + } else { 167 + return "[]" + t, nil 168 + } 169 + case lexicon.SchemaUnknown: 170 + switch gen.Config.UnknownType { 171 + case "type-decoder": 172 + return "*lexutil.LexiconTypeDecoder", nil 173 + case "json-raw-message": 174 + if optional { 175 + return "*json.RawMessage", nil 176 + } else { 177 + return "json.RawMessage", nil 178 + } 179 + case "map-string-any": 180 + return "map[string]any", nil 181 + default: 182 + return "map[string]any", nil 183 + } 184 + case lexicon.SchemaRef: 185 + ptr := "" 186 + if optional { 187 + ptr = "*" 188 + } 189 + if v.Ref == "#main" { 190 + return ptr + gen.baseName(), nil 191 + } 192 + if strings.HasPrefix(v.Ref, "#") { 193 + // local reference 194 + return fmt.Sprintf("%s%s_%s", ptr, gen.baseName(), strings.Title(v.Ref[1:])), nil 195 + } else { 196 + // external reference 197 + t, err := gen.externalRefType(v.Ref) 198 + if err != nil { 199 + return "", err 200 + } 201 + return ptr + t, nil 202 + } 203 + default: 204 + return "", fmt.Errorf("unhandled schema type in struct field: %T", def.Inner) 205 + } 206 + } 207 + 208 + func (gen *FlatGenerator) externalRefType(ref string) (string, error) { 209 + parts := strings.SplitN(ref, "#", 3) 210 + if len(parts) > 2 { 211 + return "", fmt.Errorf("failed to parse external ref: %s", ref) 212 + } 213 + nsid, err := syntax.ParseNSID(parts[0]) 214 + if err != nil { 215 + return "", fmt.Errorf("failed to parse external ref NSID (%s): %w", ref, err) 216 + } 217 + if len(parts) == 1 || parts[1] == "main" { 218 + return fmt.Sprintf("%s.%s", nsidPkgName(nsid), nsidBaseName(nsid)), nil 219 + } else { 220 + return fmt.Sprintf("%s.%s_%s", nsidPkgName(nsid), nsidBaseName(nsid), strings.Title(parts[1])), nil 221 + } 222 + } 223 + 224 + func (gen *FlatGenerator) writeStruct(ft *FlatType, obj *lexicon.SchemaObject) error { 225 + 226 + name := gen.baseName() 227 + if ft.DefName != "main" { 228 + name += "_" + strings.Title(ft.DefName) 229 + } 230 + for _, sub := range ft.Path { 231 + name += "_" + strings.Title(sub) 232 + } 233 + 234 + if ft.DefName != "main" && len(ft.Path) == 0 { 235 + fmt.Fprintf(gen.Out, "// %s is a \"%s\" in the %s schema\n", name, ft.DefName, gen.Lex.NSID) 236 + if obj.Description != nil { 237 + fmt.Fprintln(gen.Out, "//") 238 + } 239 + } 240 + if obj.Description != nil { 241 + for _, l := range strings.Split(*obj.Description, "\n") { 242 + fmt.Fprintf(gen.Out, "// %s\n", l) 243 + } 244 + } 245 + fmt.Fprintf(gen.Out, "type %s struct {\n", name) 246 + 247 + // iterate field in sorted order 248 + fieldNames := []string{} 249 + for fname := range obj.Properties { 250 + fieldNames = append(fieldNames, fname) 251 + } 252 + sort.Strings(fieldNames) 253 + 254 + for _, fname := range fieldNames { 255 + field := obj.Properties[fname] 256 + optional := false 257 + omitempty := "" 258 + if obj.IsNullable(fname) || !isRequired(obj.Required, fname) { 259 + optional = true 260 + omitempty = ",omitempty" 261 + } 262 + 263 + var t string 264 + var err error 265 + 266 + switch field.Inner.(type) { 267 + case lexicon.SchemaObject, lexicon.SchemaUnion: 268 + t = name + "_" + strings.Title(fname) 269 + if optional { 270 + t = "*" + t 271 + } 272 + default: 273 + t, err = gen.fieldType(&field, optional) 274 + if err != nil { 275 + return err 276 + } 277 + } 278 + 279 + fmt.Fprintf(gen.Out, " %s %s", strings.Title(fname), t) 280 + fmt.Fprintf(gen.Out, " `json:\"%s%s\" cborgen:\"%s%s\"`\n", fname, omitempty, fname, omitempty) 281 + } 282 + fmt.Fprintf(gen.Out, "}\n\n") 283 + 284 + return nil 285 + } 286 + 287 + func (gen *FlatGenerator) writeQuery(ft *FlatType, query *lexicon.SchemaQuery) error { 288 + name := gen.baseName() 289 + 290 + fmt.Fprintf(gen.Out, "// %s calls the XRPC method \"%s\".\n", name, gen.Lex.NSID) 291 + if query.Description != nil { 292 + fmt.Fprintln(gen.Out, "//") 293 + for _, l := range strings.Split(*query.Description, "\n") { 294 + fmt.Fprintf(gen.Out, "// %s\n", l) 295 + } 296 + } 297 + 298 + outputBytes := false 299 + outputStruct := "" 300 + if query.Output != nil && query.Output.Schema != nil { 301 + outputStruct = name + "_Output" 302 + } else if query.Output != nil && query.Output.Encoding != "" { 303 + outputBytes = true 304 + } 305 + 306 + paramNames := []string{} 307 + if query.Parameters != nil { 308 + for name := range query.Parameters.Properties { 309 + paramNames = append(paramNames, name) 310 + } 311 + } 312 + sort.Strings(paramNames) 313 + 314 + args := []string{"ctx context.Context"} 315 + reqParams := []string{} 316 + optParams := []string{} 317 + if len(paramNames) > 0 { 318 + fmt.Fprintln(gen.Out, "//") 319 + for _, name := range paramNames { 320 + param := query.Parameters.Properties[name] 321 + ptr := "*" 322 + if isRequired(query.Parameters.Required, name) { 323 + ptr = "" 324 + reqParams = append(reqParams, name) 325 + } else { 326 + optParams = append(optParams, name) 327 + } 328 + switch v := param.Inner.(type) { 329 + case lexicon.SchemaBoolean: 330 + if v.Description != nil && *v.Description != "" { 331 + fmt.Fprintf(gen.Out, "// %s: %s\n", name, *v.Description) 332 + } 333 + args = append(args, fmt.Sprintf("%s %sbool", name, ptr)) 334 + case lexicon.SchemaInteger: 335 + if v.Description != nil && *v.Description != "" { 336 + fmt.Fprintf(gen.Out, "// %s: %s\n", name, *v.Description) 337 + } 338 + args = append(args, fmt.Sprintf("%s %sint64", name, ptr)) 339 + case lexicon.SchemaString: 340 + if v.Description != nil && *v.Description != "" { 341 + fmt.Fprintf(gen.Out, "// %s: %s\n", name, *v.Description) 342 + } 343 + args = append(args, fmt.Sprintf("%s string", name)) 344 + case lexicon.SchemaArray: 345 + if v.Description != nil && *v.Description != "" { 346 + fmt.Fprintf(gen.Out, "// %s[]: %s\n", name, *v.Description) 347 + } 348 + switch v.Items.Inner.(type) { 349 + case lexicon.SchemaBoolean: 350 + args = append(args, fmt.Sprintf("%s []bool", name)) 351 + case lexicon.SchemaInteger: 352 + args = append(args, fmt.Sprintf("%s []int64", name)) 353 + case lexicon.SchemaString: 354 + args = append(args, fmt.Sprintf("%s []string", name)) 355 + default: 356 + return fmt.Errorf("unsupported parameter array type: %T", param.Inner) 357 + } 358 + default: 359 + return fmt.Errorf("unsupported parameter type: %T", param.Inner) 360 + } 361 + } 362 + } 363 + 364 + doOutParam := "" 365 + returnType := "" 366 + fmt.Fprintf(gen.Out, "func %s(%s) ", name, strings.Join(args, ", ")) 367 + if outputStruct != "" { 368 + fmt.Fprintf(gen.Out, "(*%s, error) {\n", outputStruct) 369 + fmt.Fprintf(gen.Out, " var out %s\n\n", outputStruct) 370 + doOutParam = "&out" 371 + returnType = "&out" 372 + } else if outputBytes { 373 + fmt.Fprintf(gen.Out, "([]byte, error) {\n") 374 + fmt.Fprintf(gen.Out, " buf := new(bytes.Buffer)\n\n") 375 + doOutParam = "buf" 376 + returnType = "buf.Bytes()" 377 + } else { 378 + fmt.Fprintf(gen.Out, "error {\n") 379 + doOutParam = "nil" 380 + returnType = "nil" 381 + } 382 + // TODO: switch to map[string]any 383 + fmt.Fprintf(gen.Out, " params := map[string]interface{}{}\n") 384 + for _, name := range optParams { 385 + param := query.Parameters.Properties[name] 386 + switch param.Inner.(type) { 387 + case lexicon.SchemaString: 388 + fmt.Fprintf(gen.Out, " if %s != \"\" {\n", name) 389 + fmt.Fprintf(gen.Out, " params[\"%s\"] = %s\n", name, name) 390 + fmt.Fprintf(gen.Out, " }\n") 391 + case lexicon.SchemaArray: 392 + fmt.Fprintf(gen.Out, " if len(%s) > 0 {\n", name) 393 + fmt.Fprintf(gen.Out, " params[\"%s\"] = %s\n", name, name) 394 + fmt.Fprintf(gen.Out, " }\n") 395 + default: 396 + fmt.Fprintf(gen.Out, " if %s != nil {\n", name) 397 + fmt.Fprintf(gen.Out, " params[\"%s\"] = *%s\n", name, name) 398 + fmt.Fprintf(gen.Out, " }\n") 399 + } 400 + } 401 + for _, name := range reqParams { 402 + fmt.Fprintf(gen.Out, " params[\"%s\"] = %s\n", name, name) 403 + } 404 + fmt.Fprintln(gen.Out, "") 405 + fmt.Fprintf(gen.Out, " if err := c.LexDo(ctx, lexutil.Query, \"\", \"%s\", params, nil, %s); err != nil {\n", gen.Lex.NSID, doOutParam) 406 + fmt.Fprintf(gen.Out, " return nil, err\n") 407 + fmt.Fprintf(gen.Out, " }\n") 408 + fmt.Fprintf(gen.Out, " return %s, nil\n", returnType) 409 + fmt.Fprintf(gen.Out, "}\n\n") 410 + 411 + return nil 412 + }
+49
cmd/lexgen/main.go
··· 35 35 } 36 36 app.Commands = []*cli.Command{ 37 37 cmdGenerate, 38 + cmdDev, 38 39 } 39 40 return app.Run(context.Background(), args) 40 41 } ··· 114 115 } 115 116 return nil 116 117 } 118 + 119 + var cmdDev = &cli.Command{ 120 + Name: "dev", 121 + ArgsUsage: `<file>`, 122 + Flags: []cli.Flag{ 123 + &cli.StringFlag{ 124 + Name: "lexicons-dir", 125 + Value: "./lexicons/", 126 + Usage: "base directory for project Lexicon files", 127 + Sources: cli.EnvVars("LEXICONS_DIR"), 128 + }, 129 + }, 130 + Action: runDev, 131 + } 132 + 133 + func runDev(ctx context.Context, cmd *cli.Command) error { 134 + if !cmd.Args().Present() { 135 + return fmt.Errorf("require one or more paths") 136 + } 137 + 138 + b, err := os.ReadFile(cmd.Args().First()) 139 + if err != nil { 140 + return err 141 + } 142 + 143 + var sf lexicon.SchemaFile 144 + err = json.Unmarshal(b, &sf) 145 + if err == nil { 146 + err = sf.FinishParse() 147 + } 148 + if err != nil { 149 + return err 150 + } 151 + 152 + flat, err := FlattenSchemaFile(&sf) 153 + if err != nil { 154 + return err 155 + } 156 + 157 + gen := FlatGenerator{ 158 + Config: &GenConfig{ 159 + RegisterLexiconTypeID: true, 160 + }, 161 + Lex: flat, 162 + Out: os.Stdout, 163 + } 164 + return gen.WriteLexicon() 165 + }
+96
cmd/lexgen/util.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "slices" 6 + "strings" 7 + 8 + "github.com/bluesky-social/indigo/atproto/lexicon" 9 + "github.com/bluesky-social/indigo/atproto/syntax" 10 + 11 + "golang.org/x/net/publicsuffix" 12 + ) 13 + 14 + func defType(sd *lexicon.SchemaDef) (string, error) { 15 + switch sd.Inner.(type) { 16 + case lexicon.SchemaRecord: 17 + return "record", nil 18 + case lexicon.SchemaQuery: 19 + return "query", nil 20 + case lexicon.SchemaProcedure: 21 + return "procedure", nil 22 + case lexicon.SchemaSubscription: 23 + return "subscription", nil 24 + case lexicon.SchemaPermissionSet: 25 + return "permission-set", nil 26 + case lexicon.SchemaPermission: 27 + return "permission", nil 28 + case lexicon.SchemaNull: 29 + return "null", nil 30 + case lexicon.SchemaBoolean: 31 + return "boolean", nil 32 + case lexicon.SchemaInteger: 33 + return "integer", nil 34 + case lexicon.SchemaString: 35 + return "string", nil 36 + case lexicon.SchemaBytes: 37 + return "bytes", nil 38 + case lexicon.SchemaCIDLink: 39 + return "cid-link", nil 40 + case lexicon.SchemaArray: 41 + return "array", nil 42 + case lexicon.SchemaObject: 43 + return "object", nil 44 + case lexicon.SchemaBlob: 45 + return "blob", nil 46 + case lexicon.SchemaParams: 47 + return "params", nil 48 + case lexicon.SchemaToken: 49 + return "token", nil 50 + case lexicon.SchemaRef: 51 + return "ref", nil 52 + case lexicon.SchemaUnion: 53 + return "union", nil 54 + case lexicon.SchemaUnknown: 55 + return "unknown", nil 56 + default: 57 + return "", fmt.Errorf("unhandled schema type: %T", sd.Inner) 58 + } 59 + } 60 + 61 + func isCompoundDef(sd *lexicon.SchemaDef) bool { 62 + switch sd.Inner.(type) { 63 + case lexicon.SchemaRecord, lexicon.SchemaQuery, lexicon.SchemaProcedure, lexicon.SchemaSubscription, lexicon.SchemaArray, lexicon.SchemaObject, lexicon.SchemaUnion: 64 + return true 65 + default: 66 + return false 67 + } 68 + } 69 + 70 + func nsidPkgName(nsid syntax.NSID) string { 71 + domain := strings.ToLower(nsid.Authority()) 72 + reg, err := publicsuffix.EffectiveTLDPlusOne(domain) 73 + if err != nil { 74 + return "FAIL" 75 + } 76 + parts := strings.Split(reg, ".") 77 + slices.Reverse(parts) 78 + 79 + return strings.Join(parts, "") 80 + } 81 + 82 + func nsidBaseName(nsid syntax.NSID) string { 83 + domain := strings.ToLower(nsid.Authority()) 84 + reg, err := publicsuffix.EffectiveTLDPlusOne(domain) 85 + if err != nil { 86 + return "FAIL" 87 + } 88 + rem := domain[0 : len(domain)-len(reg)] 89 + parts := strings.Split(rem, ".") 90 + slices.Reverse(parts) 91 + parts = append(parts, nsid.Name()) 92 + for i := range parts { 93 + parts[i] = strings.Title(parts[i]) 94 + } 95 + return strings.Join(parts, "") 96 + }
+25
cmd/lexgen/util_test.go
··· 1 + package main 2 + 3 + import ( 4 + "testing" 5 + 6 + "github.com/bluesky-social/indigo/atproto/syntax" 7 + 8 + "github.com/stretchr/testify/assert" 9 + ) 10 + 11 + func TestNSIDNames(t *testing.T) { 12 + assert := assert.New(t) 13 + 14 + testVectors := [][]string{ 15 + {"app.bsky.feed.post", "appbsky", "FeedPost"}, 16 + {"com.atproto.admin.deleteAccount", "comatproto", "AdminDeleteAccount"}, 17 + {"uk.ac.school.lab.COOL.project", "ukacschool", "LabCoolProject"}, 18 + } 19 + 20 + for _, vec := range testVectors { 21 + nsid := syntax.NSID(vec[0]) 22 + assert.Equal(vec[1], nsidPkgName(nsid)) 23 + assert.Equal(vec[2], nsidBaseName(nsid)) 24 + } 25 + }