1// MIT License Bluesky Social
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21package main
22
23import (
24 "errors"
25 "fmt"
26 "io/fs"
27 "log"
28 "os"
29 "path/filepath"
30 "strings"
31
32 "github.com/bluesky-social/indigo/lex"
33 "github.com/urfave/cli/v2"
34)
35
36func findSchemas(dir string, out []string) ([]string, error) {
37 err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
38 if err != nil {
39 return err
40 }
41
42 if info.IsDir() {
43 return nil
44 }
45
46 if strings.HasSuffix(path, ".json") {
47 out = append(out, path)
48 }
49
50 return nil
51 })
52 if err != nil {
53 return out, err
54 }
55
56 return out, nil
57
58}
59
60// for direct .json lexicon files or directories containing lexicon .json files, get one flat list of all paths to .json files
61func expandArgs(args []string) ([]string, error) {
62 var out []string
63 for _, a := range args {
64 st, err := os.Stat(a)
65 if err != nil {
66 return nil, err
67 }
68 if st.IsDir() {
69 out, err = findSchemas(a, out)
70 if err != nil {
71 return nil, err
72 }
73 } else if strings.HasSuffix(a, ".json") {
74 out = append(out, a)
75 }
76 }
77
78 return out, nil
79}
80
81func main() {
82 app := cli.NewApp()
83
84 app.Flags = []cli.Flag{
85 &cli.StringFlag{
86 Name: "outdir",
87 },
88 &cli.BoolFlag{
89 Name: "gen-server",
90 },
91 &cli.BoolFlag{
92 Name: "gen-handlers",
93 },
94 &cli.StringSliceFlag{
95 Name: "types-import",
96 },
97 &cli.StringSliceFlag{
98 Name: "external-lexicons",
99 },
100 &cli.StringFlag{
101 Name: "package",
102 Value: "schemagen",
103 },
104 &cli.StringFlag{
105 Name: "build",
106 Value: "",
107 },
108 &cli.StringFlag{
109 Name: "build-file",
110 Value: "",
111 },
112 }
113 app.Action = func(cctx *cli.Context) error {
114 paths, err := expandArgs(cctx.Args().Slice())
115 if err != nil {
116 return err
117 }
118
119 var schemas []*lex.Schema
120 for _, arg := range paths {
121 if strings.HasSuffix(arg, "com/atproto/temp/importRepo.json") {
122 fmt.Printf("skipping schema: %s\n", arg)
123 continue
124 }
125 s, err := lex.ReadSchema(arg)
126 if err != nil {
127 return fmt.Errorf("failed to read file %q: %w", arg, err)
128 }
129
130 schemas = append(schemas, s)
131 }
132
133 externalPaths, err := expandArgs(cctx.StringSlice("external-lexicons"))
134 if err != nil {
135 return err
136 }
137 var externalSchemas []*lex.Schema
138 for _, arg := range externalPaths {
139 s, err := lex.ReadSchema(arg)
140 if err != nil {
141 return fmt.Errorf("failed to read file %q: %w", arg, err)
142 }
143
144 externalSchemas = append(externalSchemas, s)
145 }
146
147 buildLiteral := cctx.String("build")
148 buildPath := cctx.String("build-file")
149 var packages []lex.Package
150 if buildLiteral != "" {
151 if buildPath != "" {
152 return errors.New("must not set both --build and --build-file")
153 }
154 packages, err = lex.ParsePackages([]byte(buildLiteral))
155 if err != nil {
156 return fmt.Errorf("--build error, %w", err)
157 }
158 if len(packages) == 0 {
159 return errors.New("--build must specify at least one Package{}")
160 }
161 } else if buildPath != "" {
162 blob, err := os.ReadFile(buildPath)
163 if err != nil {
164 return fmt.Errorf("--build-file error, %w", err)
165 }
166 packages, err = lex.ParsePackages(blob)
167 if err != nil {
168 return fmt.Errorf("--build-file error, %w", err)
169 }
170 if len(packages) == 0 {
171 return errors.New("--build-file must specify at least one Package{}")
172 }
173 } else {
174 return errors.New("need exactly one of --build or --build-file")
175 }
176
177 if cctx.Bool("gen-server") {
178 pkgname := cctx.String("package")
179 outdir := cctx.String("outdir")
180 if outdir == "" {
181 return fmt.Errorf("must specify output directory (--outdir)")
182 }
183 defmap := lex.BuildExtDefMap(append(schemas, externalSchemas...), packages)
184 _ = defmap
185
186 paths := cctx.StringSlice("types-import")
187 importmap := make(map[string]string)
188 for _, p := range paths {
189 parts := strings.Split(p, ":")
190 importmap[parts[0]] = parts[1]
191 }
192
193 handlers := cctx.Bool("gen-handlers")
194
195 if err := lex.CreateHandlerStub(pkgname, importmap, outdir, schemas, handlers); err != nil {
196 return err
197 }
198
199 } else {
200 return lex.Run(schemas, externalSchemas, packages)
201 }
202
203 return nil
204 }
205
206 if err := app.Run(os.Args); err != nil {
207 log.Fatal(err)
208 }
209}