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  }