github.com/octohelm/cuemod@v0.9.4/pkg/cueify/core/extractor.go (about)

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	cueast "cuelang.org/go/cue/ast"
    10  	"cuelang.org/go/cue/format"
    11  	"github.com/pkg/errors"
    12  	"golang.org/x/mod/sumdb/dirhash"
    13  
    14  	"github.com/octohelm/cuemod/internal/version"
    15  )
    16  
    17  type Extractor interface {
    18  	// Name
    19  	// Extractor name
    20  	// used in mod.cue @import("")
    21  	Name() string
    22  	// Detect check dir should use extractor.
    23  	// if matched, return deps <repo>:<version> too.
    24  	Detect(ctx context.Context, src string) (bool, map[string]string)
    25  	// Extract convert dir to cue codes
    26  	Extract(ctx context.Context, src string) ([]*cueast.File, error)
    27  }
    28  
    29  var SharedExtractors = Extractors{}
    30  
    31  func Register(extractor Extractor) {
    32  	SharedExtractors.Register(extractor)
    33  }
    34  
    35  type Extractors map[string]Extractor
    36  
    37  func (extractors Extractors) Register(extractor Extractor) {
    38  	extractors[extractor.Name()] = extractor
    39  }
    40  
    41  func (extractors Extractors) Detect(ctx context.Context, src string) (string, map[string]string) {
    42  	for _, e := range extractors {
    43  		if ok, deps := e.Detect(ctx, src); ok {
    44  			return e.Name(), deps
    45  		}
    46  	}
    47  	return "", nil
    48  }
    49  
    50  func (extractors Extractors) ExtractToDir(ctx context.Context, name string, src string, gen string) error {
    51  	if extractor, ok := extractors[name]; ok {
    52  		return extractors.do(ctx, extractor, src, gen)
    53  	}
    54  	return errors.Errorf("unsupport extractor `%s`", name)
    55  }
    56  
    57  func (Extractors) do(ctx context.Context, extractor Extractor, src string, gen string) error {
    58  	sumFile := filepath.Join(gen, ".sum")
    59  	sum, _ := os.ReadFile(sumFile)
    60  
    61  	origin, err := os.Readlink(src)
    62  	if err == nil {
    63  		src = origin
    64  	}
    65  
    66  	dirSum, err := dirhash.HashDir(src, "cuem-"+version.Version(), dirhash.DefaultHash)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	if string(sum) == dirSum {
    72  		// skip when dirSum same
    73  		return nil
    74  	}
    75  
    76  	currentFiles, err := filepath.Glob(filepath.Join(gen, "*_gen.cue"))
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	shouldDelete := map[string]bool{}
    82  
    83  	for _, f := range currentFiles {
    84  		shouldDelete[filepath.Base(f)] = true
    85  	}
    86  
    87  	files, err := extractor.Extract(ctx, src)
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	for i := range files {
    93  		shouldDelete[files[i].Filename] = false
    94  
    95  		if err := writeCueFile(ctx, extractor.Name(), gen, files[i]); err != nil {
    96  			return err
    97  		}
    98  	}
    99  
   100  	for filename, ok := range shouldDelete {
   101  		if ok {
   102  			if err := os.RemoveAll(filepath.Join(gen, filename)); err != nil {
   103  				return err
   104  			}
   105  		}
   106  	}
   107  
   108  	return writeFile(sumFile, []byte(dirSum))
   109  }
   110  
   111  func writeCueFile(ctx context.Context, name string, dir string, f *cueast.File) error {
   112  	filename := filepath.Join(dir, f.Filename)
   113  
   114  	cueast.AddComment(f.Decls[0], &cueast.CommentGroup{
   115  		Doc: true,
   116  		List: []*cueast.Comment{
   117  			{Text: "// DO NOT EDIT THIS FILE DIRECTLY."},
   118  			{Text: fmt.Sprintf("// generated by %s extractor.", name)},
   119  		},
   120  	})
   121  
   122  	data, err := format.Node(f, format.Simplify())
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	return writeFile(filename, data)
   128  }
   129  
   130  func writeFile(filename string, data []byte) error {
   131  	if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil {
   132  		return err
   133  	}
   134  	return os.WriteFile(filename, data, os.ModePerm)
   135  }