github.com/alimy/mir/v4@v4.1.0/internal/generator/generator.go (about)

     1  // Copyright 2020 Michael Li <alimy@gility.net>. All rights reserved.
     2  // Use of this source code is governed by Apache License 2.0 that
     3  // can be found in the LICENSE file.
     4  
     5  package generator
     6  
     7  import (
     8  	"errors"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"reflect"
    13  	"sync"
    14  	"text/template"
    15  
    16  	"github.com/alimy/mir/v4/core"
    17  	"github.com/alimy/mir/v4/internal/naming"
    18  	"github.com/alimy/mir/v4/internal/utils"
    19  )
    20  
    21  func init() {
    22  	core.RegisterGenerators(
    23  		&mirGenerator{name: core.GeneratorGin},
    24  		&mirGenerator{name: core.GeneratorChi},
    25  		&mirGenerator{name: core.GeneratorMux},
    26  		&mirGenerator{name: core.GeneratorHertz},
    27  		&mirGenerator{name: core.GeneratorEcho},
    28  		&mirGenerator{name: core.GeneratorIris},
    29  		&mirGenerator{name: core.GeneratorFiber},
    30  		&mirGenerator{name: core.GeneratorMacaron},
    31  		&mirGenerator{name: core.GeneratorHttpRouter},
    32  	)
    33  }
    34  
    35  type mirGenerator struct {
    36  	sinkPath  string
    37  	name      string
    38  	isCleanup bool
    39  }
    40  
    41  type mirWriter struct {
    42  	ns   naming.NamingStrategy
    43  	tmpl *template.Template
    44  }
    45  
    46  func (w *mirWriter) Write(dirPath string, iface *core.IfaceDescriptor) error {
    47  	fileName := w.ns.Naming(iface.TypeName) + ".go"
    48  	filePath := filepath.Join(dirPath, fileName)
    49  	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
    50  	if err == nil {
    51  		defer func() {
    52  			_ = file.Close()
    53  		}()
    54  		if err = w.tmpl.Execute(file, iface); err == nil {
    55  			core.Logus("generated iface: %s.%s to file: %s", iface.PkgName, iface.TypeName, filePath)
    56  		}
    57  	}
    58  	return err
    59  }
    60  
    61  // Name name of generator
    62  func (g *mirGenerator) Name() string {
    63  	return g.name
    64  }
    65  
    66  // Init init generator
    67  func (g *mirGenerator) Init(opts *core.GeneratorOpts) (err error) {
    68  	if opts == nil {
    69  		return errors.New("init opts is nil")
    70  	}
    71  	g.isCleanup = opts.Cleanup
    72  	g.sinkPath, err = evalSinkPath(opts.SinkPath)
    73  	return
    74  }
    75  
    76  // Generate serial generate interface code
    77  func (g *mirGenerator) Generate(ds core.Descriptors) error {
    78  	// cleanup out first if need
    79  	g.cleanup()
    80  	return generate(g.name, g.sinkPath, ds)
    81  }
    82  
    83  // GenerateContext concurrent generate interface code
    84  func (g *mirGenerator) GenerateContext(ctx core.MirCtx) {
    85  	tmpl, err := templateFrom(g.name)
    86  	if err != nil {
    87  		ctx.Cancel(err)
    88  		return
    89  	}
    90  	apiPath := filepath.Join(g.sinkPath, "api")
    91  	ifaceSource, _ := ctx.Pipe()
    92  	onceSet := utils.NewOnceSet(func(path string) error {
    93  		return os.MkdirAll(path, 0755)
    94  	})
    95  
    96  	// cleanup out first if need
    97  	g.cleanup()
    98  
    99  	var t *template.Template
   100  	wg := &sync.WaitGroup{}
   101  	ns := naming.NewSnakeNamingStrategy()
   102  	inOutsMap := make(map[string]utils.Set)
   103  	for iface := range ifaceSource {
   104  		dirPath := filepath.Join(apiPath, iface.Group)
   105  		if err = onceSet.Add(dirPath); err != nil {
   106  			goto FuckErr
   107  		}
   108  		if t, err = tmpl.Clone(); err != nil {
   109  			goto FuckErr
   110  		}
   111  
   112  		// setup inOuts for IfaceDescriptor
   113  		filter, exist := inOutsMap[iface.Group]
   114  		if !exist {
   115  			filter = utils.NewStrSet()
   116  			inOutsMap[iface.Group] = filter
   117  		}
   118  		var inouts []reflect.Type
   119  		for _, typ := range iface.AllInOuts() {
   120  			if typ.PkgPath() == iface.PkgPath {
   121  				if err := filter.Add(typ.Name()); err == nil {
   122  					inouts = append(inouts, typ)
   123  				}
   124  			} else {
   125  				inouts = append(inouts, typ)
   126  			}
   127  		}
   128  		iface.SetInnerInOuts(inouts)
   129  
   130  		writer := &mirWriter{tmpl: t, ns: ns}
   131  		wg.Add(1)
   132  		go func(ctx core.MirCtx, wg *sync.WaitGroup, writer *mirWriter, iface *core.IfaceDescriptor) {
   133  			defer wg.Done()
   134  
   135  			if err := writer.Write(dirPath, iface); err != nil {
   136  				ctx.Cancel(err)
   137  			}
   138  		}(ctx, wg, writer, iface)
   139  	}
   140  	wg.Wait()
   141  
   142  	ctx.GeneratorDone()
   143  	return
   144  
   145  FuckErr:
   146  	ctx.Cancel(err)
   147  }
   148  
   149  // Clone return a copy of Generator
   150  func (g *mirGenerator) Clone() core.Generator {
   151  	return &mirGenerator{
   152  		name:     g.name,
   153  		sinkPath: g.sinkPath,
   154  	}
   155  }
   156  
   157  func (g *mirGenerator) cleanup() {
   158  	if g.isCleanup {
   159  		apiPath := path.Join(g.sinkPath, "api")
   160  		core.Logus("cleanup out: %s", apiPath)
   161  		if err := os.RemoveAll(apiPath); err != nil {
   162  			core.Logus("want cleanup out first but failure: %s.do it later by yourself.", err)
   163  		}
   164  	}
   165  }
   166  
   167  func generate(generatorName string, sinkPath string, ds core.Descriptors) error {
   168  	var dirPath string
   169  
   170  	tmpl, err := templateFrom(generatorName)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	writer := &mirWriter{tmpl: tmpl, ns: naming.NewSnakeNamingStrategy()}
   175  	apiPath := filepath.Join(sinkPath, "api")
   176  
   177  FuckErr:
   178  	for key, ifaces := range ds {
   179  		group := ds.GroupFrom(key)
   180  		dirPath = filepath.Join(apiPath, group)
   181  		if err = os.MkdirAll(dirPath, 0755); err != nil {
   182  			break
   183  		}
   184  		filter := utils.NewStrSet()
   185  		hadDecribeCoreInterface := false
   186  		for _, iface := range ifaces.SortedIfaces() {
   187  			var inouts []reflect.Type
   188  			for _, typ := range iface.AllInOuts() {
   189  				if typ.PkgPath() == iface.PkgPath {
   190  					if err := filter.Add(typ.Name()); err == nil {
   191  						inouts = append(inouts, typ)
   192  					}
   193  				} else {
   194  					inouts = append(inouts, typ)
   195  				}
   196  			}
   197  			iface.SetInnerInOuts(inouts)
   198  			if !hadDecribeCoreInterface {
   199  				hadDecribeCoreInterface = true
   200  				iface.SetDeclareCoreInterface(true)
   201  			}
   202  			if err = writer.Write(dirPath, iface); err != nil {
   203  				break FuckErr
   204  			}
   205  		}
   206  	}
   207  
   208  	return err
   209  }
   210  
   211  func evalSinkPath(path string) (string, error) {
   212  	sp, err := filepath.EvalSymlinks(path)
   213  	if err != nil {
   214  		if os.IsNotExist(err) {
   215  			if !filepath.IsAbs(path) {
   216  				if sp, err = os.Getwd(); err == nil {
   217  					sp = filepath.Join(sp, path)
   218  				}
   219  			} else {
   220  				sp, err = path, nil
   221  			}
   222  		}
   223  	}
   224  	return sp, err
   225  }