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 }