github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/config/profile/module.go (about) 1 package profile 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "runtime" 9 "strings" 10 11 "github.com/lmorg/murex/app" 12 "github.com/lmorg/murex/builtins/pipes/term" 13 "github.com/lmorg/murex/lang" 14 "github.com/lmorg/murex/lang/ref" 15 "github.com/lmorg/murex/shell/autocomplete" 16 "github.com/lmorg/murex/utils" 17 "github.com/lmorg/murex/utils/ansi" 18 "github.com/lmorg/murex/utils/consts" 19 "github.com/lmorg/murex/utils/posix" 20 "github.com/lmorg/murex/utils/semver" 21 ) 22 23 // Module is the structure for each module within a module's directory. 24 // Each directory can have multiple modules - this is done so you can separate 25 // functionality into different logical modules but still keep them inside one 26 // git repository (or other source control). However I expect the typical usage 27 // would be one module per repository. 28 // 29 // This structure is loaded from module.json file located inside the root of 30 // the package. 31 type Module struct { 32 Name string 33 Summary string 34 Version string 35 Source string 36 Package string 37 Disabled bool 38 Loaded bool 39 Dependencies Dependencies 40 } 41 42 // Dependencies is a list of executables required by the module plus a list of 43 // OSs the module is expected to work against 44 type Dependencies struct { 45 Optional []string 46 Required []string 47 Platform []string 48 MurexVersion string 49 } 50 51 // Package is some basic details about the package itself as seen in the 52 // package.json file located at the rood directory inside the package itself 53 type Package struct { 54 Name string 55 Version string 56 Dependencies Dependencies 57 } 58 59 var ( 60 // Packages is a struct of all the modules 61 Packages = make(map[string][]Module) 62 63 disabled []string 64 ) 65 66 func isDisabled(name string) bool { 67 for i := range disabled { 68 if disabled[i] == name { 69 return true 70 } 71 } 72 73 return false 74 } 75 76 // Path returns the full path to the murex script that is sourced into your running shell 77 func (m *Module) Path() string { 78 return ModulePath() + m.Package + consts.PathSlash + m.Source 79 } 80 81 func (m *Module) validate() error { 82 var message string 83 if strings.TrimSpace(m.Name) == "" { 84 message += ` * Property "Name" is empty. This should contain the name of the module` + utils.NewLineString 85 } 86 87 if strings.TrimSpace(m.Summary) == "" { 88 message += ` * Property "Summary" is empty. This should contain a brief description of the module` + utils.NewLineString 89 } 90 91 if strings.TrimSpace(m.Version) == "" { 92 message += ` * Property "Version" is empty. This should contain a version number of this module` + utils.NewLineString 93 } 94 95 if strings.TrimSpace(m.Source) == "" { 96 message += " * Property \"Source\" is empty. This should contain the name (or path) of the murex script to be `source`ed into your running shell as part of this module" + utils.NewLineString 97 98 } else { 99 fi, err := os.Stat(m.Path()) 100 101 if err != nil { 102 message += fmt.Sprintf(" * Unable to stat() script `%s`: %s%s", m.Path(), err.Error(), utils.NewLineString) 103 104 } else if fi.IsDir() { 105 message += fmt.Sprintf(" * Script `%s` exists but is a directory%s", m.Path(), utils.NewLineString) 106 } 107 } 108 109 if message != "" { 110 return errors.New(message) 111 } 112 113 return m.checkDependencies() 114 } 115 116 func (m *Module) execute() error { 117 file, err := os.OpenFile(m.Path(), os.O_RDONLY, 0640) 118 if err != nil { 119 return err 120 } 121 122 defer file.Close() 123 124 b, err := io.ReadAll(file) 125 if err != nil { 126 return err 127 } 128 129 block := []rune(string(b)) 130 131 os.Stderr.WriteString(fmt.Sprintf("Loading module `%s/%s`%s", m.Package, m.Name, utils.NewLineString)) 132 133 fork := lang.ShellProcess.Fork(lang.F_NEW_MODULE | lang.F_FUNCTION | lang.F_NO_STDIN) 134 // lets redirect all output to STDERR just in case this thing gets piped 135 // for any strange reason 136 fork.Stdout = term.NewErr(false) 137 fork.Stderr = term.NewErr(ansi.IsAllowed()) 138 moduleName := m.Package + "/" + m.Name 139 fork.FileRef = &ref.File{Source: &ref.Source{Module: moduleName}} 140 fork.FileRef.Source = ref.History.AddSource(m.Path(), moduleName, b) 141 142 fork.Name.Set("(module)") 143 _, err = fork.Execute(block) 144 return err 145 } 146 147 func (m *Module) checkDependencies() error { 148 var goos []string 149 150 if len(m.Dependencies.Platform) == 0 { 151 goto checkDeps 152 } 153 154 goos = []string{runtime.GOOS, "any"} 155 if posix.IsPosix() { 156 goos = append(goos, "posix") 157 } 158 159 for _, supported := range m.Dependencies.Platform { 160 for _, host := range goos { 161 if host == supported { 162 goto checkDeps 163 } 164 } 165 } 166 167 return errors.New(" * This module isn't designed to run on " + strings.Title(runtime.GOOS)) 168 169 checkDeps: 170 var message string 171 172 // check supported version 173 if m.Dependencies.MurexVersion != "" { 174 ok, err := semver.Compare(app.Version(), m.Dependencies.MurexVersion) 175 if err != nil { 176 message += " * Error checking supported Murex version: " + err.Error() 177 } else if !ok { 178 message += " * This module is not supported for this version of Murex" 179 } 180 } 181 182 // check dependencies 183 for _, cmd := range m.Dependencies.Required { 184 if !(*autocomplete.GlobalExes.Get())[cmd] && lang.GoFunctions[cmd] == nil && !lang.MxFunctions.Exists(cmd) { 185 message += " * Missing required executable, builtin or murex function: `" + cmd + "`" + utils.NewLineString 186 } 187 } 188 189 if message != "" { 190 return errors.New(message) 191 } 192 193 return nil 194 }