github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/config/profile/packages.go (about)

     1  package profile
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/lmorg/murex/app"
    11  	"github.com/lmorg/murex/utils"
    12  	"github.com/lmorg/murex/utils/consts"
    13  	"github.com/lmorg/murex/utils/semver"
    14  )
    15  
    16  const (
    17  	// DisabledFile is an array of disabled modules
    18  	DisabledFile = "disabled.json"
    19  
    20  	// PackagesFile is used by the package manager, `murex-package`, but we auto-create
    21  	// it here for consistency
    22  	PackagesFile = "packages.json"
    23  
    24  	// IgnoredExt is an file extension which can be used on package directories
    25  	// to have them ignored during start up
    26  	IgnoredExt = ".ignore"
    27  )
    28  
    29  func modules(modulePath string) error {
    30  	// Check module path
    31  	fi, err := os.Stat(modulePath)
    32  	if os.IsNotExist(err) {
    33  		err = os.Mkdir(modulePath, 0740)
    34  		if err != nil {
    35  			return err
    36  		}
    37  
    38  	} else if !fi.IsDir() {
    39  		return errors.New("murex module path exists but is not a directory")
    40  	}
    41  
    42  	// Check module disable file
    43  	if err = disabledFile(); err != nil {
    44  		return errors.New(err.Error() + utils.NewLineString + "Skipping module loading for safety reasons")
    45  	}
    46  
    47  	// Check package management file
    48  	if err = packageFile(); err != nil {
    49  		return errors.New(err.Error() + utils.NewLineString + "This will break murex's package manager, `murex-package`, however modules will continue to work without it")
    50  	}
    51  
    52  	paths, err := filepath.Glob(modulePath + "*")
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	var message string
    58  
    59  	for i := range paths {
    60  		_, err = LoadPackage(paths[i], true)
    61  		if err != nil {
    62  			message += err.Error() + utils.NewLineString
    63  		}
    64  	}
    65  
    66  	if message != "" {
    67  		return errors.New(strings.TrimSpace(message))
    68  	}
    69  
    70  	return nil
    71  }
    72  
    73  func disabledFile() error {
    74  	err := autoFile(DisabledFile)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	return ReadJson(ModulePath()+DisabledFile, &disabled)
    80  }
    81  
    82  func packageFile() error {
    83  	return autoFile(PackagesFile)
    84  }
    85  
    86  func autoFile(name string) error {
    87  	filename := ModulePath() + name
    88  
    89  	fi, err := os.Stat(filename)
    90  	switch {
    91  	case os.IsNotExist(err):
    92  		file, err := os.OpenFile(filename, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0640)
    93  		if err != nil {
    94  			return err
    95  		}
    96  		defer file.Close()
    97  		_, err = file.WriteString("[]")
    98  		return err
    99  
   100  	case fi.IsDir():
   101  		return errors.New(name + " is a directory - it should be an ordinary file")
   102  
   103  	case err != nil:
   104  		return err
   105  
   106  	default:
   107  		return nil
   108  	}
   109  }
   110  
   111  // LoadPackage reads in the contents of the package and then validates and
   112  // sources each module within. The path value should be an absolute path.
   113  func LoadPackage(path string, execute bool) ([]Module, error) {
   114  	// Because we are expecting an absolute path and any errors with it being
   115  	// relative will have been compiled into the Go code, we want to raise a
   116  	// panic here so those errors get caught during testing rather than buggy
   117  	// code getting pushed back to the master branch and thus released.
   118  	if !filepath.IsAbs(path) {
   119  		panic("relative path used in LoadPackage")
   120  	}
   121  
   122  	f, err := os.Stat(path)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	// file is not a directory thus not a module
   128  	if !f.IsDir() {
   129  		return nil, nil
   130  	}
   131  
   132  	// ignore hidden directories. eg version control (.git), IDE workspace
   133  	// settings, OS X metadirectories and other guff.
   134  	if strings.HasPrefix(f.Name(), ".") {
   135  		return nil, nil
   136  	}
   137  
   138  	// disable package directory (this goes further than disabling the module
   139  	// because it prevents the modules from even being read)
   140  	if strings.HasSuffix(f.Name(), IgnoredExt) {
   141  		return nil, nil
   142  	}
   143  
   144  	var message string
   145  
   146  	// load package
   147  	var pkg Package
   148  	err = ReadJson(path+consts.PathSlash+"package.json", &pkg)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	if pkg.Dependencies.MurexVersion != "" {
   154  		ok, err := semver.Compare(app.Version(), pkg.Dependencies.MurexVersion)
   155  		if err != nil {
   156  			message += fmt.Sprintf("* Package '%s': Error checking supported Murex version: %s\n", pkg.Name, err.Error())
   157  		} else if !ok {
   158  			message += fmt.Sprintf("* Package '%s': Package not supported (%s) for this version of Murex (%s)\n", pkg.Name, pkg.Dependencies.MurexVersion, app.Version())
   159  		}
   160  	}
   161  
   162  	// load modules
   163  
   164  	var module []Module
   165  	err = ReadJson(path+consts.PathSlash+"module.json", &module)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	pwd, err := os.Getwd()
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	for i := range module {
   176  		module[i].Package = f.Name()
   177  		module[i].Disabled = module[i].Disabled || isDisabled(module[i].Package+"/"+module[i].Name)
   178  		err = module[i].validate()
   179  		if err != nil && !module[i].Disabled {
   180  			message += fmt.Sprintf(
   181  				"* Package '%s': Error loading module `%s` in path `%s`:%s%s%s",
   182  				pkg.Name,
   183  				module[i].Name,
   184  				module[i].Path(),
   185  				utils.NewLineString,
   186  				err.Error(),
   187  				utils.NewLineString,
   188  			)
   189  			continue
   190  		}
   191  
   192  		if !execute || module[i].Disabled {
   193  			continue
   194  		}
   195  
   196  		err = os.Chdir(path)
   197  		if err != nil {
   198  			os.Stderr.WriteString(err.Error())
   199  		}
   200  
   201  		module[i].Loaded = true
   202  
   203  		err = module[i].execute()
   204  		if err != nil {
   205  			message += fmt.Sprintf(
   206  				"* Package '%s': Error sourcing module `%s` in path `%s`:%s%s%s",
   207  				pkg.Name,
   208  				module[i].Name,
   209  				module[i].Path(),
   210  				utils.NewLineString,
   211  				err.Error(),
   212  				utils.NewLineString,
   213  			)
   214  		}
   215  	}
   216  
   217  	if execute {
   218  		Packages[f.Name()] = module
   219  
   220  		err = os.Chdir(pwd)
   221  		if err != nil {
   222  			message += err.Error() + utils.NewLineString
   223  		}
   224  	}
   225  
   226  	if message != "" {
   227  		return module, errors.New(strings.TrimSpace(message))
   228  	}
   229  
   230  	return module, nil
   231  }