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  }