github.com/mlmmr/revel-cmd@v0.21.2-0.20191112133115-68d8795776dd/model/revel_container.go (about) 1 // This package will be shared between Revel and Revel CLI eventually 2 package model 3 4 import ( 5 "github.com/mlmmr/revel-cmd/utils" 6 "github.com/revel/config" 7 "go/build" 8 9 "errors" 10 "fmt" 11 "path/filepath" 12 "sort" 13 "strings" 14 ) 15 16 type ( 17 // The container object for describing all Revels variables 18 RevelContainer struct { 19 BuildPaths struct { 20 Revel string 21 } 22 Paths struct { 23 Import string 24 Source string 25 Base string 26 App string 27 Views string 28 Code []string 29 Template []string 30 Config []string 31 } 32 PackageInfo struct { 33 Config config.Context 34 Packaged bool 35 DevMode bool 36 Vendor bool 37 } 38 Application struct { 39 Name string 40 Root string 41 } 42 43 ImportPath string // The import path 44 SourcePath string // The full source path 45 RunMode string // The current run mode 46 RevelPath string // The path to the Revel source code 47 BasePath string // The base path to the application 48 AppPath string // The application path (BasePath + "/app") 49 ViewsPath string // The application views path 50 CodePaths []string // All the code paths 51 TemplatePaths []string // All the template paths 52 ConfPaths []string // All the configuration paths 53 Config *config.Context // The global config object 54 Packaged bool // True if packaged 55 DevMode bool // True if running in dev mode 56 HTTPPort int // The http port 57 HTTPAddr string // The http address 58 HTTPSsl bool // True if running https 59 HTTPSslCert string // The SSL certificate 60 HTTPSslKey string // The SSL key 61 AppName string // The application name 62 AppRoot string // The application root from the config `app.root` 63 CookiePrefix string // The cookie prefix 64 CookieDomain string // The cookie domain 65 CookieSecure bool // True if cookie is secure 66 SecretStr string // The secret string 67 MimeConfig *config.Context // The mime configuration 68 ModulePathMap map[string]string // The module path map 69 } 70 71 WrappedRevelCallback struct { 72 FireEventFunction func(key Event, value interface{}) (response EventResponse) 73 ImportFunction func(pkgName string) error 74 } 75 ) 76 77 // Simple Wrapped RevelCallback 78 func NewWrappedRevelCallback(fe func(key Event, value interface{}) (response EventResponse), ie func(pkgName string) error) RevelCallback { 79 return &WrappedRevelCallback{fe, ie} 80 } 81 82 // Function to implement the FireEvent 83 func (w *WrappedRevelCallback) FireEvent(key Event, value interface{}) (response EventResponse) { 84 if w.FireEventFunction != nil { 85 response = w.FireEventFunction(key, value) 86 } 87 return 88 } 89 func (w *WrappedRevelCallback) PackageResolver(pkgName string) error { 90 return w.ImportFunction(pkgName) 91 } 92 93 // RevelImportPath Revel framework import path 94 var RevelImportPath = "github.com/revel/revel" 95 var RevelModulesImportPath = "github.com/revel/modules" 96 97 // This function returns a container object describing the revel application 98 // eventually this type of function will replace the global variables. 99 func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp *RevelContainer, err error) { 100 rp = &RevelContainer{ModulePathMap: map[string]string{}} 101 // Ignore trailing slashes. 102 rp.ImportPath = strings.TrimRight(importPath, "/") 103 rp.SourcePath = srcPath 104 rp.RunMode = mode 105 106 // If the SourcePath is not specified, find it using build.Import. 107 var revelSourcePath string // may be different from the app source path 108 if rp.SourcePath == "" { 109 rp.SourcePath, revelSourcePath, err = utils.FindSrcPaths(importPath, RevelImportPath, callback.PackageResolver) 110 if err != nil { 111 return 112 } 113 } else { 114 // If the SourcePath was specified, assume both Revel and the app are within it. 115 rp.SourcePath = filepath.Clean(rp.SourcePath) 116 revelSourcePath = rp.SourcePath 117 } 118 119 // Setup paths for application 120 rp.RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath)) 121 rp.BasePath = filepath.Join(rp.SourcePath, filepath.FromSlash(importPath)) 122 rp.PackageInfo.Vendor = utils.Exists(filepath.Join(rp.BasePath, "vendor")) 123 rp.AppPath = filepath.Join(rp.BasePath, "app") 124 125 // Sanity check , ensure app and conf paths exist 126 if !utils.DirExists(rp.AppPath) { 127 return rp, fmt.Errorf("No application found at path %s", rp.AppPath) 128 } 129 if !utils.DirExists(filepath.Join(rp.BasePath, "conf")) { 130 return rp, fmt.Errorf("No configuration found at path %s", filepath.Join(rp.BasePath, "conf")) 131 } 132 133 rp.ViewsPath = filepath.Join(rp.AppPath, "views") 134 rp.CodePaths = []string{rp.AppPath} 135 rp.TemplatePaths = []string{} 136 137 if rp.ConfPaths == nil { 138 rp.ConfPaths = []string{} 139 } 140 141 // Config load order 142 // 1. framework (revel/conf/*) 143 // 2. application (conf/*) 144 // 3. user supplied configs (...) - User configs can override/add any from above 145 rp.ConfPaths = append( 146 []string{ 147 filepath.Join(rp.RevelPath, "conf"), 148 filepath.Join(rp.BasePath, "conf"), 149 }, 150 rp.ConfPaths...) 151 152 rp.Config, err = config.LoadContext("app.conf", rp.ConfPaths) 153 if err != nil { 154 return rp, fmt.Errorf("Unable to load configuartion file %s", err) 155 } 156 157 // Ensure that the selected runmode appears in app.conf. 158 // If empty string is passed as the mode, treat it as "DEFAULT" 159 if mode == "" { 160 mode = config.DefaultSection 161 } 162 if !rp.Config.HasSection(mode) { 163 return rp, fmt.Errorf("app.conf: No mode found: %s %s", "run-mode", mode) 164 } 165 rp.Config.SetSection(mode) 166 167 // Configure properties from app.conf 168 rp.DevMode = rp.Config.BoolDefault("mode.dev", false) 169 rp.HTTPPort = rp.Config.IntDefault("http.port", 9000) 170 rp.HTTPAddr = rp.Config.StringDefault("http.addr", "") 171 rp.HTTPSsl = rp.Config.BoolDefault("http.ssl", false) 172 rp.HTTPSslCert = rp.Config.StringDefault("http.sslcert", "") 173 rp.HTTPSslKey = rp.Config.StringDefault("http.sslkey", "") 174 if rp.HTTPSsl { 175 if rp.HTTPSslCert == "" { 176 return rp, errors.New("No http.sslcert provided.") 177 } 178 if rp.HTTPSslKey == "" { 179 return rp, errors.New("No http.sslkey provided.") 180 } 181 } 182 // 183 rp.AppName = rp.Config.StringDefault("app.name", "(not set)") 184 rp.AppRoot = rp.Config.StringDefault("app.root", "") 185 rp.CookiePrefix = rp.Config.StringDefault("cookie.prefix", "REVEL") 186 rp.CookieDomain = rp.Config.StringDefault("cookie.domain", "") 187 rp.CookieSecure = rp.Config.BoolDefault("cookie.secure", rp.HTTPSsl) 188 rp.SecretStr = rp.Config.StringDefault("app.secret", "") 189 190 callback.FireEvent(REVEL_BEFORE_MODULES_LOADED, nil) 191 if err := rp.loadModules(callback); err != nil { 192 return rp, err 193 } 194 195 callback.FireEvent(REVEL_AFTER_MODULES_LOADED, nil) 196 197 return 198 } 199 200 // LoadMimeConfig load mime-types.conf on init. 201 func (rp *RevelContainer) LoadMimeConfig() (err error) { 202 rp.MimeConfig, err = config.LoadContext("mime-types.conf", rp.ConfPaths) 203 if err != nil { 204 return fmt.Errorf("Failed to load mime type config: %s %s", "error", err) 205 } 206 return 207 } 208 209 // Loads modules based on the configuration setup. 210 // This will fire the REVEL_BEFORE_MODULE_LOADED, REVEL_AFTER_MODULE_LOADED 211 // for each module loaded. The callback will receive the RevelContainer, name, moduleImportPath and modulePath 212 // It will automatically add in the code paths for the module to the 213 // container object 214 func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) { 215 keys := []string{} 216 for _, key := range rp.Config.Options("module.") { 217 keys = append(keys, key) 218 } 219 220 // Reorder module order by key name, a poor mans sort but at least it is consistent 221 sort.Strings(keys) 222 for _, key := range keys { 223 moduleImportPath := rp.Config.StringDefault(key, "") 224 if moduleImportPath == "" { 225 continue 226 } 227 228 modulePath, err := rp.ResolveImportPath(moduleImportPath) 229 if err != nil { 230 utils.Logger.Info("Missing module ", "module_import_path", moduleImportPath, "error",err) 231 callback.PackageResolver(moduleImportPath) 232 modulePath, err = rp.ResolveImportPath(moduleImportPath) 233 if err != nil { 234 return fmt.Errorf("Failed to load module. Import of path failed %s:%s %s:%s ", "modulePath", moduleImportPath, "error", err) 235 } 236 } 237 // Drop anything between module.???.<name of module> 238 name := key[len("module."):] 239 if index := strings.Index(name, "."); index > -1 { 240 name = name[index+1:] 241 } 242 callback.FireEvent(REVEL_BEFORE_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath}) 243 rp.addModulePaths(name, moduleImportPath, modulePath) 244 callback.FireEvent(REVEL_AFTER_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath}) 245 } 246 return 247 } 248 249 // Adds a module paths to the container object 250 func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) { 251 if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) { 252 rp.CodePaths = append(rp.CodePaths, codePath) 253 rp.ModulePathMap[name] = modulePath 254 if viewsPath := filepath.Join(modulePath, "app", "views"); utils.DirExists(viewsPath) { 255 rp.TemplatePaths = append(rp.TemplatePaths, viewsPath) 256 } 257 } 258 259 // Hack: There is presently no way for the testrunner module to add the 260 // "test" subdirectory to the CodePaths. So this does it instead. 261 if importPath == rp.Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") { 262 joinedPath := filepath.Join(rp.BasePath, "tests") 263 rp.CodePaths = append(rp.CodePaths, joinedPath) 264 } 265 if testsPath := filepath.Join(modulePath, "tests"); utils.DirExists(testsPath) { 266 rp.CodePaths = append(rp.CodePaths, testsPath) 267 } 268 } 269 270 // ResolveImportPath returns the filesystem path for the given import path. 271 // Returns an error if the import path could not be found. 272 func (rp *RevelContainer) ResolveImportPath(importPath string) (string, error) { 273 if rp.Packaged { 274 return filepath.Join(rp.SourcePath, importPath), nil 275 } 276 277 modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly) 278 if err != nil { 279 return "", err 280 } 281 if rp.PackageInfo.Vendor && !strings.HasPrefix(modPkg.Dir,rp.BasePath) { 282 return "", fmt.Errorf("Module %s was found outside of path %s.",importPath, modPkg.Dir) 283 } 284 return modPkg.Dir, nil 285 }