Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / module.go
1 package revel
2
3 import (
4         "fmt"
5         "github.com/revel/revel/logger"
6         "go/build"
7         "gopkg.in/stack.v0"
8         "path/filepath"
9         "sort"
10         "strings"
11 )
12
13 // Module specific functions
14 type Module struct {
15         Name, ImportPath, Path string
16         ControllerTypeList     []*ControllerType
17         Log                    logger.MultiLogger
18         initializedModules     map[string]ModuleCallbackInterface
19 }
20
21 // Modules can be called back after they are loaded in revel by using this interface.
22 type ModuleCallbackInterface func(*Module)
23
24 // The namespace separator constant
25 const namespaceSeperator = `\` // (note cannot be . or : as this is already used for routes)
26
27 var (
28         Modules   []*Module                                                                                     // The list of modules in use
29         anyModule = &Module{}                                                                                   // Wildcard search for controllers for a module (for backward compatible lookups)
30         appModule = &Module{Name: "App", initializedModules: map[string]ModuleCallbackInterface{}, Log: AppLog} // The app module
31         moduleLog = RevelLog.New("section", "module")
32 )
33
34 // Called by a module init() function, caller will receive the *Module object created for that module
35 // This would be useful for assigning a logger for logging information in the module (since the module context would be correct)
36 func RegisterModuleInit(callback ModuleCallbackInterface) {
37         // Store the module that called this so we can do a callback when the app is initialized
38         // The format %+k is from go-stack/Call.Format and returns the package path
39         key := fmt.Sprintf("%+k", stack.Caller(1))
40         appModule.initializedModules[key] = callback
41         if Initialized {
42                 RevelLog.Error("Application already initialized, initializing using app module", "key", key)
43                 callback(appModule)
44         }
45 }
46
47 // Called on startup to make a callback so that modules can be initialized through the `RegisterModuleInit` function
48 func init() {
49         AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
50                 if typeOf == REVEL_BEFORE_MODULES_LOADED {
51                         Modules = []*Module{appModule}
52                         appModule.Path = filepath.ToSlash(AppPath)
53                         appModule.ImportPath = filepath.ToSlash(AppPath)
54                 }
55
56                 return
57         })
58 }
59
60 // Returns the namespace for the module in the format `module_name|`
61 func (m *Module) Namespace() (namespace string) {
62         namespace = m.Name + namespaceSeperator
63         return
64 }
65
66 // Returns the named controller and action that is in this module
67 func (m *Module) ControllerByName(name, action string) (ctype *ControllerType) {
68         comparison := name
69         if strings.Index(name, namespaceSeperator) < 0 {
70                 comparison = m.Namespace() + name
71         }
72         for _, c := range m.ControllerTypeList {
73                 if c.Name() == comparison {
74                         ctype = c
75                         break
76                 }
77         }
78         return
79 }
80
81 // Adds the controller type to this module
82 func (m *Module) AddController(ct *ControllerType) {
83         m.ControllerTypeList = append(m.ControllerTypeList, ct)
84 }
85
86 // Based on the full path given return the relevant module
87 // Only to be used on initialization
88 func ModuleFromPath(path string, addGopathToPath bool) (module *Module) {
89         path = filepath.ToSlash(path)
90         gopathList := filepath.SplitList(build.Default.GOPATH)
91         // Strip away the vendor folder
92         if i := strings.Index(path, "/vendor/"); i > 0 {
93                 path = path[i+len("vendor/"):]
94         }
95
96         // See if the path exists in the module based
97         for i := range Modules {
98                 if addGopathToPath {
99                         for _, gopath := range gopathList {
100                                 if strings.Contains(filepath.ToSlash(filepath.Clean(filepath.Join(gopath, "src", path))), Modules[i].Path) {
101                                         module = Modules[i]
102                                         break
103                                 }
104                         }
105                 } else {
106                         if strings.Contains(path, Modules[i].ImportPath) {
107                                 module = Modules[i]
108                                 break
109                         }
110
111                 }
112
113                 if module != nil {
114                         break
115                 }
116         }
117         // Default to the app module if not found
118         if module == nil {
119                 module = appModule
120         }
121         return
122 }
123
124 // ModuleByName returns the module of the given name, if loaded, case insensitive.
125 func ModuleByName(name string) (*Module, bool) {
126         // If the name ends with the namespace separator remove it
127         if name[len(name)-1] == []byte(namespaceSeperator)[0] {
128                 name = name[:len(name)-1]
129         }
130         name = strings.ToLower(name)
131         if name == strings.ToLower(appModule.Name) {
132                 return appModule, true
133         }
134         for _, module := range Modules {
135                 if strings.ToLower(module.Name) == name {
136                         return module, true
137                 }
138         }
139         return nil, false
140 }
141
142 // Loads the modules specified in the config
143 func loadModules() {
144         keys := []string{}
145         for _, key := range Config.Options("module.") {
146                 keys = append(keys, key)
147         }
148
149         // Reorder module order by key name, a poor mans sort but at least it is consistent
150         sort.Strings(keys)
151         for _, key := range keys {
152                 moduleLog.Debug("Sorted keys", "keys", key)
153
154         }
155         for _, key := range keys {
156                 moduleImportPath := Config.StringDefault(key, "")
157                 if moduleImportPath == "" {
158                         continue
159                 }
160
161                 modulePath, err := ResolveImportPath(moduleImportPath)
162                 if err != nil {
163                         moduleLog.Error("Failed to load module.  Import of path failed", "modulePath", moduleImportPath, "error", err)
164                 }
165                 // Drop anything between module.???.<name of module>
166                 subKey := key[len("module."):]
167                 if index := strings.Index(subKey, "."); index > -1 {
168                         subKey = subKey[index+1:]
169                 }
170                 addModule(subKey, moduleImportPath, modulePath)
171         }
172
173         // Modules loaded, now show module path
174         for key, callback := range appModule.initializedModules {
175                 if m := ModuleFromPath(key, false); m != nil {
176                         callback(m)
177                 } else {
178                         RevelLog.Error("Callback for non registered module initializing with application module", "modulePath", key)
179                         callback(appModule)
180                 }
181         }
182 }
183
184 // called by `loadModules`, creates a new `Module` instance and appends it to the `Modules` list
185 func addModule(name, importPath, modulePath string) {
186         if _, found := ModuleByName(name); found {
187                 moduleLog.Panic("Attempt to import duplicate module %s path %s aborting startup", "name", name, "path", modulePath)
188         }
189         Modules = append(Modules, &Module{Name: name,
190                 ImportPath: filepath.ToSlash(importPath),
191                 Path:       filepath.ToSlash(modulePath),
192                 Log:        RootLog.New("module", name)})
193         if codePath := filepath.Join(modulePath, "app"); DirExists(codePath) {
194                 CodePaths = append(CodePaths, codePath)
195                 if viewsPath := filepath.Join(modulePath, "app", "views"); DirExists(viewsPath) {
196                         TemplatePaths = append(TemplatePaths, viewsPath)
197                 }
198         }
199
200         moduleLog.Debug("Loaded module ", "module", filepath.Base(modulePath))
201
202         // Hack: There is presently no way for the testrunner module to add the
203         // "test" subdirectory to the CodePaths.  So this does it instead.
204         if importPath == Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") {
205                 joinedPath := filepath.Join(BasePath, "tests")
206                 moduleLog.Debug("Found testrunner module, adding `tests` path ", "path", joinedPath)
207                 CodePaths = append(CodePaths, joinedPath)
208         }
209         if testsPath := filepath.Join(modulePath, "tests"); DirExists(testsPath) {
210                 moduleLog.Debug("Found tests path ", "path", testsPath)
211                 CodePaths = append(CodePaths, testsPath)
212         }
213 }