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 }