github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/std/memfile.go (about) 1 package std 2 3 import ( 4 "fmt" 5 "regexp" 6 "sort" 7 "strings" 8 ) 9 10 type MemFile struct { 11 Name string 12 Body string 13 } 14 15 // MemPackage represents the information and files of a package which will be 16 // stored in memory. It will generally be initialized by package gnolang's 17 // ReadMemPackage. 18 // 19 // NOTE: in the future, a MemPackage may represent 20 // updates/additional-files for an existing package. 21 type MemPackage struct { 22 Name string // package name as declared by `package` 23 Path string // import path 24 Files []*MemFile 25 } 26 27 func (mempkg *MemPackage) GetFile(name string) *MemFile { 28 for _, memFile := range mempkg.Files { 29 if memFile.Name == name { 30 return memFile 31 } 32 } 33 return nil 34 } 35 36 func (mempkg *MemPackage) IsEmpty() bool { 37 return len(mempkg.Files) == 0 38 } 39 40 var ( 41 rePkgName = regexp.MustCompile(`^[a-z][a-z0-9_]*$`) 42 rePkgOrRlmPath = regexp.MustCompile(`^gno\.land\/(?:p|r)(?:\/_?[a-z]+[a-z0-9_]*)+$`) 43 reFileName = regexp.MustCompile(`^([a-zA-Z0-9_]*\.[a-z0-9_\.]*|LICENSE|README)$`) 44 ) 45 46 // path must not contain any dots after the first domain component. 47 // file names must contain dots. 48 // NOTE: this is to prevent conflicts with nested paths. 49 func (mempkg *MemPackage) Validate() error { 50 // add assertion that MemPkg contains at least 1 file 51 if len(mempkg.Files) <= 0 { 52 return fmt.Errorf("no files found within package %q", mempkg.Name) 53 } 54 55 if !rePkgName.MatchString(mempkg.Name) { 56 return fmt.Errorf("invalid package name %q, failed to match %q", mempkg.Name, rePkgName) 57 } 58 if !rePkgOrRlmPath.MatchString(mempkg.Path) { 59 return fmt.Errorf("invalid package/realm path %q, failed to match %q", mempkg.Path, rePkgOrRlmPath) 60 } 61 // enforce sorting files based on Go conventions for predictability 62 sorted := sort.SliceIsSorted( 63 mempkg.Files, 64 func(i, j int) bool { 65 return mempkg.Files[i].Name < mempkg.Files[j].Name 66 }, 67 ) 68 if !sorted { 69 return fmt.Errorf("mempackage %q has unsorted files", mempkg.Path) 70 } 71 72 var prev string 73 for i, file := range mempkg.Files { 74 if !reFileName.MatchString(file.Name) { 75 return fmt.Errorf("invalid file name %q, failed to match %q", file.Name, reFileName) 76 } 77 if i > 0 && prev == file.Name { 78 return fmt.Errorf("duplicate file name %q", file.Name) 79 } 80 prev = file.Name 81 } 82 83 return nil 84 } 85 86 // Splits a path into the dirpath and filename. 87 func SplitFilepath(filepath string) (dirpath string, filename string) { 88 parts := strings.Split(filepath, "/") 89 if len(parts) == 1 { 90 return parts[0], "" 91 } 92 last := parts[len(parts)-1] 93 if strings.Contains(last, ".") { 94 return strings.Join(parts[:len(parts)-1], "/"), last 95 } else if last == "" { 96 return strings.Join(parts[:len(parts)-1], "/"), "" 97 } else { 98 return strings.Join(parts, "/"), "" 99 } 100 }