github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/cmd/generates/command.go (about) 1 /* 2 * Copyright 2023 Wang Min Xiang 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package generates 19 20 import ( 21 "context" 22 "fmt" 23 "github.com/aacfactory/errors" 24 "github.com/aacfactory/fns/cmd/generates/files" 25 "github.com/aacfactory/fns/cmd/generates/modules" 26 "github.com/aacfactory/fns/cmd/generates/sources" 27 "github.com/urfave/cli/v2" 28 "path/filepath" 29 "runtime" 30 "strings" 31 ) 32 33 func WithName(name string) Option { 34 return func(options *Options) { 35 options.name = name 36 } 37 } 38 39 func WithModulesDir(dir string) Option { 40 return func(options *Options) { 41 options.modulesDir = dir 42 } 43 } 44 45 func WithAnnotations(annotations ...modules.FnAnnotationCodeWriter) Option { 46 return func(options *Options) { 47 if options.annotations == nil { 48 options.annotations = make([]modules.FnAnnotationCodeWriter, 0, 1) 49 } 50 options.annotations = append(options.annotations, annotations...) 51 } 52 } 53 54 func WithBuiltinTypes(builtinTypes ...*sources.Type) Option { 55 return func(options *Options) { 56 if options.builtinTypes == nil { 57 options.builtinTypes = make([]*sources.Type, 0, 1) 58 } 59 options.builtinTypes = append(options.builtinTypes, builtinTypes...) 60 } 61 } 62 63 func WithGenerator(generator Generator) Option { 64 return func(options *Options) { 65 if options.generators == nil { 66 options.generators = make([]Generator, 0, 1) 67 } 68 options.generators = append(options.generators, generator) 69 } 70 } 71 72 type Option func(options *Options) 73 74 type Options struct { 75 name string 76 modulesDir string 77 annotations []modules.FnAnnotationCodeWriter 78 builtinTypes []*sources.Type 79 generators []Generator 80 } 81 82 func New(options ...Option) (cmd Command) { 83 opt := Options{} 84 for _, option := range options { 85 option(&opt) 86 } 87 name := opt.name 88 if name == "" { 89 name = callerPKG() 90 } 91 act := &action{ 92 modulesDir: opt.modulesDir, 93 annotations: opt.annotations, 94 builtinTypes: opt.builtinTypes, 95 generators: opt.generators, 96 } 97 // app 98 app := cli.NewApp() 99 app.Name = name 100 app.Flags = []cli.Flag{ 101 &cli.BoolFlag{ 102 Name: "verbose", 103 EnvVars: []string{"FNS_VERBOSE"}, 104 Aliases: []string{"v"}, 105 Usage: "verbose output", 106 Required: false, 107 }, 108 &cli.StringFlag{ 109 Name: "work", 110 Aliases: []string{"w"}, 111 Usage: "set workspace file path", 112 Required: false, 113 EnvVars: []string{"FNS_WORK"}, 114 TakesFile: false, 115 }, 116 } 117 app.Usage = fmt.Sprintf("%s {project path}", name) 118 app.Action = act.Handle 119 120 // cmd 121 cmd = &cliCommand{ 122 app: app, 123 } 124 return 125 } 126 127 type Command interface { 128 Execute(ctx context.Context, args ...string) (err error) 129 } 130 131 type cliCommand struct { 132 app *cli.App 133 } 134 135 func (c *cliCommand) Execute(ctx context.Context, args ...string) (err error) { 136 err = c.app.RunContext(ctx, args) 137 return 138 } 139 140 type action struct { 141 modulesDir string 142 annotations []modules.FnAnnotationCodeWriter 143 builtinTypes []*sources.Type 144 generators []Generator 145 } 146 147 func (act *action) Handle(c *cli.Context) (err error) { 148 // verbose 149 verbose := c.Bool("verbose") 150 // project dir 151 projectDir := strings.TrimSpace(c.Args().First()) 152 if projectDir == "" { 153 projectDir = "." 154 } 155 if !filepath.IsAbs(projectDir) { 156 projectDir, err = filepath.Abs(projectDir) 157 if err != nil { 158 err = errors.Warning("generates: generate failed").WithCause(err).WithMeta("dir", projectDir) 159 return 160 } 161 } 162 projectDir = filepath.ToSlash(projectDir) 163 work := c.String("work") 164 if work != "" { 165 work = strings.TrimSpace(work) 166 if work == "" { 167 err = errors.Warning("generates: generate failed").WithCause(errors.Warning("work option is invalid")) 168 return 169 } 170 } else { 171 parentDir := filepath.Dir(projectDir) 172 if parentDir != "" { 173 if files.ExistFile(filepath.Join(parentDir, "go.work")) { 174 work = filepath.Join(parentDir, "go.work") 175 } 176 } 177 } 178 ctx := c.Context 179 // parse mod 180 moduleFilename := filepath.Join(projectDir, "go.mod") 181 var mod *sources.Module 182 if work != "" { 183 mod, err = sources.NewWithWork(moduleFilename, work) 184 } else { 185 mod, err = sources.New(moduleFilename) 186 } 187 if err != nil { 188 err = errors.Warning("generates: generate failed").WithCause(err) 189 return 190 } 191 parseModErr := mod.Parse(ctx) 192 if parseModErr != nil { 193 err = errors.Warning("generates: generate failed").WithCause(parseModErr) 194 return 195 } 196 if len(act.builtinTypes) > 0 { 197 for _, builtinType := range act.builtinTypes { 198 mod.RegisterBuiltinType(builtinType) 199 } 200 } 201 // services 202 services := modules.NewGenerator(act.modulesDir, act.annotations, verbose) 203 servicesErr := services.Generate(ctx, mod) 204 if servicesErr != nil { 205 err = errors.Warning("generates: generate failed").WithCause(servicesErr) 206 return 207 } 208 // extras 209 for _, generator := range act.generators { 210 err = generator.Generate(ctx, mod) 211 if err != nil { 212 err = errors.Warning("generates: generate failed").WithCause(err) 213 return 214 } 215 } 216 return 217 } 218 219 func callerPKG() string { 220 _, file, _, ok := runtime.Caller(2) 221 if ok { 222 dir := filepath.Dir(file) 223 if dir == "" { 224 return "fns" 225 } 226 return filepath.Base(dir) 227 } 228 return "fns" 229 }