// This package will be shared between Revel and Revel CLI eventually package model import ( "github.com/revel/cmd/utils" "github.com/revel/config" "go/build" "errors" "fmt" "path/filepath" "sort" "strings" ) type ( // The container object for describing all Revels variables RevelContainer struct { BuildPaths struct { Revel string } Paths struct { Import string Source string Base string App string Views string Code []string Template []string Config []string } PackageInfo struct { Config config.Context Packaged bool DevMode bool Vendor bool } Application struct { Name string Root string } ImportPath string // The import path SourcePath string // The full source path RunMode string // The current run mode RevelPath string // The path to the Revel source code BasePath string // The base path to the application AppPath string // The application path (BasePath + "/app") ViewsPath string // The application views path CodePaths []string // All the code paths TemplatePaths []string // All the template paths ConfPaths []string // All the configuration paths Config *config.Context // The global config object Packaged bool // True if packaged DevMode bool // True if running in dev mode HTTPPort int // The http port HTTPAddr string // The http address HTTPSsl bool // True if running https HTTPSslCert string // The SSL certificate HTTPSslKey string // The SSL key AppName string // The application name AppRoot string // The application root from the config `app.root` CookiePrefix string // The cookie prefix CookieDomain string // The cookie domain CookieSecure bool // True if cookie is secure SecretStr string // The secret string MimeConfig *config.Context // The mime configuration ModulePathMap map[string]string // The module path map } WrappedRevelCallback struct { FireEventFunction func(key Event, value interface{}) (response EventResponse) ImportFunction func(pkgName string) error } ) // Simple Wrapped RevelCallback func NewWrappedRevelCallback(fe func(key Event, value interface{}) (response EventResponse), ie func(pkgName string) error) RevelCallback { return &WrappedRevelCallback{fe, ie} } // Function to implement the FireEvent func (w *WrappedRevelCallback) FireEvent(key Event, value interface{}) (response EventResponse) { if w.FireEventFunction != nil { response = w.FireEventFunction(key, value) } return } func (w *WrappedRevelCallback) PackageResolver(pkgName string) error { return w.ImportFunction(pkgName) } // RevelImportPath Revel framework import path var RevelImportPath = "github.com/revel/revel" var RevelModulesImportPath = "github.com/revel/modules" // This function returns a container object describing the revel application // eventually this type of function will replace the global variables. func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp *RevelContainer, err error) { rp = &RevelContainer{ModulePathMap: map[string]string{}} // Ignore trailing slashes. rp.ImportPath = strings.TrimRight(importPath, "/") rp.SourcePath = srcPath rp.RunMode = mode // If the SourcePath is not specified, find it using build.Import. var revelSourcePath string // may be different from the app source path if rp.SourcePath == "" { rp.SourcePath, revelSourcePath, err = utils.FindSrcPaths(importPath, RevelImportPath, callback.PackageResolver) if err != nil { return } } else { // If the SourcePath was specified, assume both Revel and the app are within it. rp.SourcePath = filepath.Clean(rp.SourcePath) revelSourcePath = rp.SourcePath } // Setup paths for application rp.RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath)) rp.BasePath = filepath.Join(rp.SourcePath, filepath.FromSlash(importPath)) rp.PackageInfo.Vendor = utils.Exists(filepath.Join(rp.BasePath, "vendor")) rp.AppPath = filepath.Join(rp.BasePath, "app") // Sanity check , ensure app and conf paths exist if !utils.DirExists(rp.AppPath) { return rp, fmt.Errorf("No application found at path %s", rp.AppPath) } if !utils.DirExists(filepath.Join(rp.BasePath, "conf")) { return rp, fmt.Errorf("No configuration found at path %s", filepath.Join(rp.BasePath, "conf")) } rp.ViewsPath = filepath.Join(rp.AppPath, "views") rp.CodePaths = []string{rp.AppPath} rp.TemplatePaths = []string{} if rp.ConfPaths == nil { rp.ConfPaths = []string{} } // Config load order // 1. framework (revel/conf/*) // 2. application (conf/*) // 3. user supplied configs (...) - User configs can override/add any from above rp.ConfPaths = append( []string{ filepath.Join(rp.RevelPath, "conf"), filepath.Join(rp.BasePath, "conf"), }, rp.ConfPaths...) rp.Config, err = config.LoadContext("app.conf", rp.ConfPaths) if err != nil { return rp, fmt.Errorf("Unable to load configuartion file %s", err) } // Ensure that the selected runmode appears in app.conf. // If empty string is passed as the mode, treat it as "DEFAULT" if mode == "" { mode = config.DefaultSection } if !rp.Config.HasSection(mode) { return rp, fmt.Errorf("app.conf: No mode found: %s %s", "run-mode", mode) } rp.Config.SetSection(mode) // Configure properties from app.conf rp.DevMode = rp.Config.BoolDefault("mode.dev", false) rp.HTTPPort = rp.Config.IntDefault("http.port", 9000) rp.HTTPAddr = rp.Config.StringDefault("http.addr", "") rp.HTTPSsl = rp.Config.BoolDefault("http.ssl", false) rp.HTTPSslCert = rp.Config.StringDefault("http.sslcert", "") rp.HTTPSslKey = rp.Config.StringDefault("http.sslkey", "") if rp.HTTPSsl { if rp.HTTPSslCert == "" { return rp, errors.New("No http.sslcert provided.") } if rp.HTTPSslKey == "" { return rp, errors.New("No http.sslkey provided.") } } // rp.AppName = rp.Config.StringDefault("app.name", "(not set)") rp.AppRoot = rp.Config.StringDefault("app.root", "") rp.CookiePrefix = rp.Config.StringDefault("cookie.prefix", "REVEL") rp.CookieDomain = rp.Config.StringDefault("cookie.domain", "") rp.CookieSecure = rp.Config.BoolDefault("cookie.secure", rp.HTTPSsl) rp.SecretStr = rp.Config.StringDefault("app.secret", "") callback.FireEvent(REVEL_BEFORE_MODULES_LOADED, nil) if err := rp.loadModules(callback); err != nil { return rp, err } callback.FireEvent(REVEL_AFTER_MODULES_LOADED, nil) return } // LoadMimeConfig load mime-types.conf on init. func (rp *RevelContainer) LoadMimeConfig() (err error) { rp.MimeConfig, err = config.LoadContext("mime-types.conf", rp.ConfPaths) if err != nil { return fmt.Errorf("Failed to load mime type config: %s %s", "error", err) } return } // Loads modules based on the configuration setup. // This will fire the REVEL_BEFORE_MODULE_LOADED, REVEL_AFTER_MODULE_LOADED // for each module loaded. The callback will receive the RevelContainer, name, moduleImportPath and modulePath // It will automatically add in the code paths for the module to the // container object func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) { keys := []string{} for _, key := range rp.Config.Options("module.") { keys = append(keys, key) } // Reorder module order by key name, a poor mans sort but at least it is consistent sort.Strings(keys) for _, key := range keys { moduleImportPath := rp.Config.StringDefault(key, "") if moduleImportPath == "" { continue } modulePath, err := rp.ResolveImportPath(moduleImportPath) if err != nil { utils.Logger.Info("Missing module ", "module_import_path", moduleImportPath, "error",err) callback.PackageResolver(moduleImportPath) modulePath, err = rp.ResolveImportPath(moduleImportPath) if err != nil { return fmt.Errorf("Failed to load module. Import of path failed %s:%s %s:%s ", "modulePath", moduleImportPath, "error", err) } } // Drop anything between module.???. name := key[len("module."):] if index := strings.Index(name, "."); index > -1 { name = name[index+1:] } callback.FireEvent(REVEL_BEFORE_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath}) rp.addModulePaths(name, moduleImportPath, modulePath) callback.FireEvent(REVEL_AFTER_MODULE_LOADED, []interface{}{rp, name, moduleImportPath, modulePath}) } return } // Adds a module paths to the container object func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) { if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) { rp.CodePaths = append(rp.CodePaths, codePath) rp.ModulePathMap[name] = modulePath if viewsPath := filepath.Join(modulePath, "app", "views"); utils.DirExists(viewsPath) { rp.TemplatePaths = append(rp.TemplatePaths, viewsPath) } } // Hack: There is presently no way for the testrunner module to add the // "test" subdirectory to the CodePaths. So this does it instead. if importPath == rp.Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") { joinedPath := filepath.Join(rp.BasePath, "tests") rp.CodePaths = append(rp.CodePaths, joinedPath) } if testsPath := filepath.Join(modulePath, "tests"); utils.DirExists(testsPath) { rp.CodePaths = append(rp.CodePaths, testsPath) } } // ResolveImportPath returns the filesystem path for the given import path. // Returns an error if the import path could not be found. func (rp *RevelContainer) ResolveImportPath(importPath string) (string, error) { if rp.Packaged { return filepath.Join(rp.SourcePath, importPath), nil } modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly) if err != nil { return "", err } if rp.PackageInfo.Vendor && !strings.HasPrefix(modPkg.Dir,rp.BasePath) { return "", fmt.Errorf("Module %s was found outside of path %s.",importPath, modPkg.Dir) } return modPkg.Dir, nil }