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  }