Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

The "rev package" and "rev clean" commands

- "rev package" can package an app to run it somewhere without a go
installation
- "rev clean" removes the generated files from an app
- Config: Add versions of property getters that allow caller to provide
a default to return instead of saying "not found"
- Do some refactoring of the harness build/run stuff
  • Loading branch information...
commit 86302320e2b9fe4257f352112ab337b3e2276cd0 1 parent 7a31a5b
@robfig robfig authored
View
48 cmd/clean.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "fmt"
+ "go/build"
+ "os"
+ "path"
+)
+
+var cmdClean = &Command{
+ UsageLine: "clean [import path]",
+ Short: "clean a Revel application's temp files",
+ Long: `~
+~ Clean the Revel web application named by the given import path.
+~
+~ For example:
+~
+~ rev clean github.com/robfig/revel/samples/chat
+~
+~ It removes the app/tmp directory.
+`,
+}
+
+func init() {
+ cmdClean.Run = cleanApp
+}
+
+func cleanApp(args []string) {
+ if len(args) == 0 {
+ fmt.Fprintf(os.Stderr, cmdClean.Long)
+ return
+ }
+
+ appPkg, err := build.Import(args[0], "", build.FindOnly)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "~ Abort: Failed to find import path:", err)
+ return
+ }
+
+ // Remove the app/tmp directory.
+ tmpDir := path.Join(appPkg.Dir, "app", "tmp")
+ fmt.Println("~ Removing:", tmpDir)
+ err = os.RemoveAll(tmpDir)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "~ Abort:", err)
+ return
+ }
+}
View
92 cmd/new.go
@@ -3,13 +3,10 @@ package main
import (
"fmt"
"go/build"
- "io/ioutil"
"math/rand"
"os"
"path"
"path/filepath"
- "strings"
- "text/template"
)
var cmdNew = &Command{
@@ -52,94 +49,19 @@ func newApp(args []string) {
}
err = os.MkdirAll(args[0], 0777)
- if err != nil {
- fmt.Fprintln(os.Stderr, "~ Abort: Failed to create directory:", err)
- return
- }
+ panicOnError(err, "Failed to create directory "+args[0])
skeletonBase = path.Join(revelPkg.Dir, "skeleton")
- appDir, err = filepath.Abs(args[0])
- if err != nil {
- fmt.Fprintln(os.Stderr, "~ Abort: Failed to get absolute directory:", err)
- return
- }
-
- err = filepath.Walk(skeletonBase, copySkeleton)
- if err != nil {
- fmt.Fprintln(os.Stderr, "~ Failed to copy skeleton:", err)
- }
+ appDir = args[0]
+ mustCopyDir(appDir, skeletonBase, map[string]interface{}{
+ // app.conf
+ "AppName": filepath.Base(appDir),
+ "Secret": genSecret(),
+ })
fmt.Fprintln(os.Stdout, "~ Your application is ready:\n~ ", appDir)
}
-// copySkeleton copies the skeleton app tree over to a new directory.
-func copySkeleton(skelPath string, skelInfo os.FileInfo, err error) error {
- // Get the relative path from the skeleton base, and the corresponding path in
- // the app directory.
- relSkelPath := strings.TrimLeft(skelPath[len(skeletonBase):], string(os.PathSeparator))
- appFile := path.Join(appDir, relSkelPath)
-
- if len(relSkelPath) == 0 {
- return nil
- }
-
- // Create a subdirectory if necessary.
- if skelInfo.IsDir() {
- err := os.Mkdir(path.Join(appDir, relSkelPath), 0777)
- if err != nil {
- fmt.Fprintln(os.Stderr, "~ Failed to create directory:", err)
- return err
- }
- return nil
- }
-
- // If this is app.conf, we have to render it as a template.
- if relSkelPath == "conf/app.conf" {
- tmpl, err := template.ParseFiles(skelPath)
- if err != nil || tmpl == nil {
- fmt.Fprintln(os.Stderr, "Failed to parse skeleton app.conf as a template:", err)
- return err
- }
-
- f, err := os.Create(appFile)
- if err != nil {
- fmt.Fprintln(os.Stderr, "Failed to create app.conf:", err)
- return err
- }
-
- err = tmpl.Execute(f, map[string]string{
- "AppName": filepath.Base(appDir),
- "Secret": genSecret(),
- })
-
- if err != nil {
- fmt.Fprintln(os.Stderr, "Failed to render template:", err)
- return err
- }
-
- err = f.Close()
- if err != nil {
- fmt.Fprintln(os.Stderr, "Failed to close app.conf:", err)
- return err
- }
- return nil
- }
-
- // Copy over the files.
- skelBytes, err := ioutil.ReadFile(skelPath)
- if err != nil {
- fmt.Fprintln(os.Stderr, "~ Failed to read file:", err)
- return err
- }
-
- err = ioutil.WriteFile(appFile, skelBytes, 0666)
- if err != nil {
- fmt.Fprintln(os.Stderr, "~ Failed to write file:", err)
- return err
- }
- return nil
-}
-
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
func genSecret() string {
View
78 cmd/package.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+ "fmt"
+ "github.com/robfig/revel"
+ "github.com/robfig/revel/harness"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+)
+
+var cmdPackage = &Command{
+ UsageLine: "package [import path]",
+ Short: "package a Revel application (e.g. for deployment)",
+ Long: `~
+~ Package the Revel web application named by the given import path.
+~ This allows it to be deployed and run on a machine that lacks a Go installation.
+~
+~ For example:
+~
+~ rev package github.com/robfig/revel/samples/chat
+~`,
+}
+
+func init() {
+ cmdPackage.Run = packageApp
+}
+
+func packageApp(args []string) {
+ if len(args) == 0 {
+ fmt.Fprint(os.Stderr, cmdPackage.Long)
+ return
+ }
+
+ appImportPath := args[0]
+ rev.Init("", appImportPath, "")
+
+ binName, reverr := harness.Build()
+ panicOnError(reverr, "Failed to build")
+
+ // Start collecting stuff in a temp directory.
+ tmpDir, err := ioutil.TempDir("", rev.AppName)
+ panicOnError(err, "Failed to get temp dir")
+
+ // Included are:
+ // - run scripts
+ // - binary
+ // - revel
+ // - app
+
+ // Revel and the app are in a directory structure mirroring import path
+ tmpRevelPath := path.Join(tmpDir, filepath.FromSlash(rev.REVEL_IMPORT_PATH))
+ mustCopyFile(path.Join(tmpDir, filepath.Base(binName)), binName)
+ mustCopyDir(path.Join(tmpRevelPath, "conf"), path.Join(rev.RevelPath, "conf"), nil)
+ mustCopyDir(path.Join(tmpRevelPath, "templates"), path.Join(rev.RevelPath, "templates"), nil)
+ mustCopyDir(path.Join(tmpDir, filepath.FromSlash(appImportPath)), rev.BasePath, nil)
+
+ tmplData := map[string]interface{}{
+ "BinName": filepath.Base(binName),
+ "ImportPath": appImportPath,
+ }
+
+ mustRenderTemplate(
+ path.Join(tmpDir, "run.sh"),
+ path.Join(rev.RevelPath, "cmd", "package_run.sh.template"),
+ tmplData)
+
+ mustRenderTemplate(
+ path.Join(tmpDir, "run.bat"),
+ path.Join(rev.RevelPath, "cmd", "package_run.bat.template"),
+ tmplData)
+
+ // Create the zip file.
+ zipName := mustZipDir(rev.AppName+".zip", tmpDir)
+
+ fmt.Println("~ Your archive is ready:", zipName)
+}
View
2  cmd/package_run.bat.template
@@ -0,0 +1,2 @@
+@echo off
+{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD% -runMode prod
View
7 cmd/package_run.sh.template
@@ -0,0 +1,7 @@
+#!/bin/bash #!/bin/sh
+#!/bin/bash
+pushd `dirname $0` > /dev/null
+SCRIPTPATH=`pwd`
+popd > /dev/null
+chmod u+x $SCRIPTPATH/{{.BinName}}
+$SCRIPTPATH/{{.BinName}} -importPath {{.ImportPath}} -srcPath $SCRIPTPATH -runMode prod
View
13 cmd/rev.go
@@ -28,6 +28,8 @@ func (cmd *Command) Name() string {
var commands = []*Command{
cmdRun,
cmdNew,
+ cmdClean,
+ cmdPackage,
}
func main() {
@@ -48,6 +50,17 @@ func main() {
usage()
}
+ // Commands use panic to abort execution when something goes wrong.
+ // Panics are logged at the point of error. Ignore those.
+ defer func() {
+ if err := recover(); err != nil {
+ if _, ok := err.(LoggedError); !ok {
+ // This panic was not expected / logged.
+ panic(err)
+ }
+ }
+ }()
+
for _, cmd := range commands {
if cmd.Name() == args[0] {
cmd.Run(args[1:])
View
7 cmd/run.go
@@ -37,8 +37,9 @@ func runApp(args []string) {
}
// Find and parse app.conf
- rev.Init(args[0], mode)
- log.Printf("Running app (%s): %s (%s)\n", mode, rev.AppName, rev.BasePath)
+ rev.Init(mode, args[0], "")
+ log.Printf("Running %s (%s) in %s mode\n", rev.AppName, rev.ImportPath, mode)
+ rev.TRACE.Println("Base path:", rev.BasePath)
- harness.Run(rev.Config.BoolDefault("server.watcher", true))
+ harness.StartApp(rev.Config.BoolDefault("server.watcher", true))
}
View
129 cmd/util.go
@@ -0,0 +1,129 @@
+package main
+
+import (
+ "archive/zip"
+ "fmt"
+ "github.com/robfig/revel"
+ "io"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "text/template"
+)
+
+// Use a wrapper to differentiate logged panics from unexpected ones.
+type LoggedError struct{ error }
+
+func panicOnError(err error, msg string) {
+ if revErr, ok := err.(*rev.Error); (ok && revErr != nil) || (!ok && err != nil) {
+ fmt.Fprintf(os.Stderr, "~ Abort: %s: %s\n", msg, err)
+ panic(LoggedError{err})
+ }
+}
+
+func mustCopyFile(destFilename, srcFilename string) {
+ destFile, err := os.Create(destFilename)
+ panicOnError(err, "Failed to create file "+destFilename)
+
+ srcFile, err := os.Open(srcFilename)
+ panicOnError(err, "Failed to open file "+srcFilename)
+
+ _, err = io.Copy(destFile, srcFile)
+ panicOnError(err,
+ fmt.Sprintf("Failed to copy data from %s to %s", srcFile.Name(), destFile.Name()))
+
+ err = destFile.Close()
+ panicOnError(err, "Failed to close file "+destFile.Name())
+
+ err = srcFile.Close()
+ panicOnError(err, "Failed to close file "+srcFile.Name())
+}
+
+func mustRenderTemplate(destPath, srcPath string, data map[string]interface{}) {
+ tmpl, err := template.ParseFiles(srcPath)
+ panicOnError(err, "Failed to parse template "+srcPath)
+
+ f, err := os.Create(destPath)
+ panicOnError(err, "Failed to create "+destPath)
+
+ err = tmpl.Execute(f, data)
+ panicOnError(err, "Failed to render template "+srcPath)
+
+ err = f.Close()
+ panicOnError(err, "Failed to close "+f.Name())
+}
+
+// copyDir copies a directory tree over to a new directory. Any files ending in
+// ".template" are treated as a Go template and rendered using the given data.
+// Additionally, the trailing ".template" is stripped from the file name.
+// Also, dot files and dot directories are skipped.
+func mustCopyDir(destDir, srcDir string, data map[string]interface{}) error {
+ return filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
+ // Get the relative path from the source base, and the corresponding path in
+ // the dest directory.
+ relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator))
+ destPath := path.Join(destDir, relSrcPath)
+
+ // Skip dot files and dot directories.
+ if strings.HasPrefix(relSrcPath, ".") {
+ if info.IsDir() {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+
+ // Create a subdirectory if necessary.
+ if info.IsDir() {
+ err := os.MkdirAll(path.Join(destDir, relSrcPath), 0777)
+ if !os.IsExist(err) {
+ panicOnError(err, "Failed to create directory")
+ }
+ return nil
+ }
+
+ // If this file ends in ".template", render it as a template.
+ if strings.HasSuffix(relSrcPath, ".template") {
+ mustRenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data)
+ return nil
+ }
+
+ // Else, just copy it over.
+ mustCopyFile(destPath, srcPath)
+ return nil
+ })
+}
+
+func mustZipDir(destFilename, srcDir string) string {
+ zipFile, err := os.Create(destFilename)
+ panicOnError(err, "Failed to create zip file")
+
+ w := zip.NewWriter(zipFile)
+ filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
+ // Ignore directories (they are represented by the path of written entries).
+ if info.IsDir() {
+ return nil
+ }
+
+ relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator))
+
+ f, err := w.Create(relSrcPath)
+ panicOnError(err, "Failed to create zip entry")
+
+ srcFile, err := os.Open(srcPath)
+ panicOnError(err, "Failed to read source file")
+
+ _, err = io.Copy(f, srcFile)
+ panicOnError(err, "Failed to copy")
+
+ return nil
+ })
+
+ err = w.Close()
+ panicOnError(err, "Failed to close archive")
+
+ err = zipFile.Close()
+ panicOnError(err, "Failed to close zip file")
+
+ return zipFile.Name()
+}
View
18 config.go
@@ -1,7 +1,9 @@
package rev
import (
+ "errors"
"github.com/kless/goconfig/config"
+ "path"
)
// This handles the parsing of application.conf
@@ -13,12 +15,18 @@ type MergedConfig struct {
section string // Check this section first, then fall back to DEFAULT
}
-func LoadConfig(fname string) (*MergedConfig, error) {
- conf, err := config.ReadDefault(fname)
- if err != nil {
- return nil, err
+func LoadConfig(confName string) (*MergedConfig, error) {
+ var err error
+ for _, confPath := range ConfPaths {
+ conf, err := config.ReadDefault(path.Join(confPath, confName))
+ if err == nil {
+ return &MergedConfig{conf, ""}, nil
+ }
+ }
+ if err == nil {
+ err = errors.New("not found")
}
- return &MergedConfig{conf, ""}, nil
+ return nil, err
}
// The top-level keys are in a section called "DEFAULT". The DEFAULT is used as
View
53 harness/harness.go
@@ -13,6 +13,8 @@ package harness
import (
"fmt"
"github.com/robfig/revel"
+ "io"
+ "net"
"net/http"
"net/http/httputil"
"net/url"
@@ -61,9 +63,7 @@ func (hp *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func NewHarness() *Harness {
// Get a template loader to render errors.
// Prefer the app's views/errors directory, and fall back to the stock error pages.
- rev.MainTemplateLoader = rev.NewTemplateLoader(
- rev.ViewsPath,
- rev.RevelTemplatePath)
+ rev.MainTemplateLoader = rev.NewTemplateLoader(rev.TemplatePaths)
rev.MainTemplateLoader.Refresh()
port := getFreePort()
@@ -76,8 +76,15 @@ func NewHarness() *Harness {
return harness
}
+// Rebuild the Revel application and run it on the given port.
func (h *Harness) Refresh() *rev.Error {
- return rebuild("", h.port)
+ rev.TRACE.Println("Rebuild")
+ binName, err := Build()
+ if err != nil {
+ return err
+ }
+ start(binName, "", h.port)
+ return nil
}
func (h *Harness) WatchDir(info os.FileInfo) bool {
@@ -108,3 +115,41 @@ func (h *Harness) Run() {
rev.ERROR.Fatalln("Failed to start reverse proxy:", err)
}
}
+
+// proxyWebsocket copies data between websocket client and server until one side
+// closes the connection. (ReverseProxy doesn't work with websocket requests.)
+func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
+ d, err := net.Dial("tcp", host)
+ if err != nil {
+ http.Error(w, "Error contacting backend server.", 500)
+ rev.ERROR.Printf("Error dialing websocket backend %s: %v", host, err)
+ return
+ }
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Not a hijacker?", 500)
+ return
+ }
+ nc, _, err := hj.Hijack()
+ if err != nil {
+ rev.ERROR.Printf("Hijack error: %v", err)
+ return
+ }
+ defer nc.Close()
+ defer d.Close()
+
+ err = r.Write(d)
+ if err != nil {
+ rev.ERROR.Printf("Error copying request to target: %v", err)
+ return
+ }
+
+ errc := make(chan error, 2)
+ cp := func(dst io.Writer, src io.Reader) {
+ _, err := io.Copy(dst, src)
+ errc <- err
+ }
+ go cp(d, nc)
+ go cp(nc, d)
+ <-errc
+}
View
84 harness/run.go
@@ -7,7 +7,6 @@ import (
"go/build"
"io"
"net"
- "net/http"
"os"
"os/exec"
"path"
@@ -23,13 +22,15 @@ var (
)
// Run the Revel program, optionally using the harness.
-func Run(useHarness bool) {
+func StartApp(useHarness bool) {
// If we are in prod mode, just build and run the application.
if !useHarness {
rev.INFO.Println("Building...")
- if err := rebuild(getAppAddress(), getAppPort()); err != nil {
+ binName, err := Build()
+ if err != nil {
rev.ERROR.Fatalln(err)
}
+ start(binName, getAppAddress(), getAppPort())
cmd.Wait()
return
}
@@ -47,12 +48,15 @@ func Run(useHarness bool) {
harness.Run()
}
-// Rebuild the Revel application and run it on the given port.
-func rebuild(addr string, port int) (compileError *rev.Error) {
- rev.TRACE.Println("Rebuild")
+// Build the app:
+// 1. Generate the the main.go file.
+// 2. Run the appropriate "go build" command.
+// Requires that rev.Init has been called previously.
+// Returns the path to the built binary, and an error if there was a problem building it.
+func Build() (binaryPath string, compileError *rev.Error) {
controllerSpecs, compileError := ScanControllers(path.Join(rev.AppPath, "controllers"))
if compileError != nil {
- return compileError
+ return "", compileError
}
tmpl := template.New("RegisterControllers")
@@ -61,7 +65,6 @@ func rebuild(addr string, port int) (compileError *rev.Error) {
"AppName": rev.AppName,
"Controllers": controllerSpecs,
"ImportPaths": uniqueImportPaths(controllerSpecs),
- "RunMode": rev.RunMode,
})
// Terminate the server if it's already running.
@@ -117,25 +120,31 @@ func rebuild(addr string, port int) (compileError *rev.Error) {
// If we failed to build, parse the error message.
if err != nil {
- return newCompileError(output)
+ return "", newCompileError(output)
}
+ return binName, nil
+}
+
+// Start the application server, waiting until it has started up.
+// Panics if startup fails.
+func start(binName, addr string, port int) {
// Run the server, via tmp/main.go.
cmd = exec.Command(binName,
- fmt.Sprintf("-addr=%s", addr),
fmt.Sprintf("-port=%d", port),
- fmt.Sprintf("-importPath=%s", rev.ImportPath))
+ fmt.Sprintf("-importPath=%s", rev.ImportPath),
+ fmt.Sprintf("-runMode=%s", rev.RunMode),
+ )
rev.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
listeningWriter := startupListeningWriter{os.Stdout, make(chan bool)}
cmd.Stdout = listeningWriter
cmd.Stderr = os.Stderr
- err = cmd.Start()
+ err := cmd.Start()
if err != nil {
rev.ERROR.Fatalln("Error running:", err)
}
<-listeningWriter.notifyReady
- return nil
}
// A io.Writer that copies to the destination, and listens for "Listening on.."
@@ -243,44 +252,6 @@ func newCompileError(output []byte) *rev.Error {
return compileError
}
-// proxyWebsocket copies data between websocket client and server until one side
-// closes the connection. (ReverseProxy doesn't work with websocket requests.)
-func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
- d, err := net.Dial("tcp", host)
- if err != nil {
- http.Error(w, "Error contacting backend server.", 500)
- rev.ERROR.Printf("Error dialing websocket backend %s: %v", host, err)
- return
- }
- hj, ok := w.(http.Hijacker)
- if !ok {
- http.Error(w, "Not a hijacker?", 500)
- return
- }
- nc, _, err := hj.Hijack()
- if err != nil {
- rev.ERROR.Printf("Hijack error: %v", err)
- return
- }
- defer nc.Close()
- defer d.Close()
-
- err = r.Write(d)
- if err != nil {
- rev.ERROR.Printf("Error copying request to target: %v", err)
- return
- }
-
- errc := make(chan error, 2)
- cp := func(dst io.Writer, src io.Reader) {
- _, err := io.Copy(dst, src)
- errc <- err
- }
- go cp(d, nc)
- go cp(nc, d)
- <-errc
-}
-
const REGISTER_CONTROLLERS = `package main
import (
@@ -293,9 +264,10 @@ import (
)
var (
- addr *string = flag.String("addr", "", "Address to listen on")
- port *int = flag.Int("port", 0, "Port")
- importPath *string = flag.String("importPath", "", "Path to the app.")
+ runMode *string = flag.String("runMode", "", "Run mode.")
+ port *int = flag.Int("port", 0, "By default, read from app.conf")
+ importPath *string = flag.String("importPath", "", "Go Import Path for the app.")
+ srcPath *string = flag.String("srcPath", "", "Path to the source root.")
// So compiler won't complain if the generated code doesn't reference reflect package...
_ = reflect.Invalid
@@ -304,7 +276,7 @@ var (
func main() {
rev.INFO.Println("Running revel server")
flag.Parse()
- rev.Init(*importPath, "{{.RunMode}}")
+ rev.Init(*runMode, *importPath, *srcPath)
{{range $i, $c := .Controllers}}
rev.RegisterController((*{{.PackageName}}.{{.StructName}})(nil),
[]*rev.MethodType{
@@ -322,6 +294,6 @@ func main() {
{{end}}
})
{{end}}
- rev.Run(*addr, *port)
+ rev.Run(*port)
}
`
View
96 revel.go
@@ -10,20 +10,28 @@ import (
"strings"
)
+const (
+ REVEL_IMPORT_PATH = "github.com/robfig/revel"
+)
+
var (
// App details
AppName string // e.g. "sample"
- BasePath string // e.g. "/Users/robfig/gocode/src/revel/sample"
- AppPath string // e.g. "/Users/robfig/gocode/src/revel/sample/app"
- ViewsPath string // e.g. "/Users/robfig/gocode/src/revel/sample/app/views"
- ImportPath string // e.g. "revel/sample"
+ BasePath string // e.g. "/Users/robfig/gocode/src/corp/sample"
+ AppPath string // e.g. "/Users/robfig/gocode/src/corp/sample/app"
+ ViewsPath string // e.g. "/Users/robfig/gocode/src/corp/sample/app/views"
+ ImportPath string // e.g. "corp/sample"
Config *MergedConfig
RunMode string // Application-defined (by default, "dev" or "prod")
// Revel installation details
- RevelPath string // e.g. "/Users/robfig/gocode/src/revel"
- RevelTemplatePath string // e.g. "/Users/robfig/gocode/src/revel/templates"
+ RevelPath string // e.g. "/Users/robfig/gocode/src/revel"
+
+ // Where to look for templates and configuration.
+ // Ordered by priority. (Earlier paths take precedence over later paths.)
+ ConfPaths []string
+ TemplatePaths []string
DEFAULT = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)
TRACE = DEFAULT
@@ -43,38 +51,48 @@ func init() {
log.SetFlags(DEFAULT.Flags())
}
-func Init(importPath string, mode string) {
+// Init initializes Revel -- it provides paths for getting around the app.
+//
+// Params:
+// mode - the run mode, which determines which app.conf settings are used.
+// importPath - the Go import path of the application.
+// srcPath - the path to the source directory, containing Revel and the app.
+// If not specified (""), then a functioning Go installation is required.
+func Init(mode, importPath, srcPath string) {
+ // Ignore trailing slashes.
+ ImportPath = strings.TrimRight(importPath, "/")
RunMode = mode
- // Find the user's app path.
- importPath = strings.TrimRight(importPath, "/")
- pkg, err := build.Import(importPath, "", build.FindOnly)
- if err != nil {
- log.Fatalln("Failed to import", importPath, "with error:", err)
- }
- BasePath = pkg.Dir
- if BasePath == "" {
- log.Fatalf("Failed to find code. Did you pass the import path?")
+ if srcPath == "" {
+ srcPath = findSrcPath(importPath)
}
- AppName = filepath.Base(BasePath)
+
+ RevelPath = path.Join(srcPath, filepath.FromSlash(REVEL_IMPORT_PATH))
+ BasePath = path.Join(srcPath, filepath.FromSlash(importPath))
AppPath = path.Join(BasePath, "app")
ViewsPath = path.Join(AppPath, "views")
- ImportPath = importPath
- // Find the provided resources.
- revelPkg, err := build.Import("github.com/robfig/revel", "", build.FindOnly)
- if err != nil {
- log.Fatalf("Failed to find revel code.")
+ ConfPaths = []string{
+ path.Join(BasePath, "conf"),
+ path.Join(RevelPath, "conf"),
}
- RevelPath = revelPkg.Dir
- RevelTemplatePath = path.Join(RevelPath, "templates")
- // Load application.conf
- Config, err = LoadConfig(path.Join(BasePath, "conf", "app.conf"))
- if err != nil {
+ TemplatePaths = []string{
+ ViewsPath,
+ path.Join(RevelPath, "templates"),
+ }
+
+ // Load app.conf
+ var err error
+ Config, err = LoadConfig("app.conf")
+ if err != nil || Config == nil {
log.Fatalln("Failed to load app.conf:", 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 = defaultSection
+ }
if !Config.HasSection(mode) {
log.Fatalln("app.conf: No mode found:", mode)
}
@@ -85,6 +103,8 @@ func Init(importPath string, mode string) {
}
secretKey = []byte(secretStr)
+ AppName = Config.StringDefault("app.name", "(not set)")
+
// Configure logging.
TRACE = getLogger("trace")
INFO = getLogger("info")
@@ -141,6 +161,28 @@ func newLogger(wr io.Writer) *log.Logger {
return log.New(wr, DEFAULT.Prefix(), DEFAULT.Flags())
}
+// findSrcPath uses the "go/build" package to find the source root.
+func findSrcPath(importPath string) string {
+ appPkg, err := build.Import(importPath, "", build.FindOnly)
+ if err != nil {
+ log.Fatalln("Failed to import", importPath, "with error:", err)
+ }
+
+ revelPkg, err := build.Import(REVEL_IMPORT_PATH, "", build.FindOnly)
+ if err != nil {
+ log.Fatalln("Failed to find Revel with error:", err)
+ }
+
+ // Find the source path from each, and ensure they are equal.
+ if revelPkg.SrcRoot != appPkg.SrcRoot {
+ log.Fatalln("Revel must be installed in the same GOPATH as your app."+
+ "\nRevel source root:", revelPkg.SrcRoot,
+ "\nApp source root:", appPkg.SrcRoot)
+ }
+
+ return appPkg.SrcRoot
+}
+
func CheckInit() {
if !revelInit {
panic("Revel has not been initialized!")
View
1  samples/chat/conf/app.conf
@@ -1,5 +1,6 @@
app.name=chat
app.secret=pJLzyoiDe17L36mytqC912j81PfTiolHm1veQK6Grn1En3YFdB5lvEHVTwFEaWvj
+http.addr=
http.port=9000
[dev]
View
17 server.go
@@ -102,17 +102,22 @@ func handleInternal(w http.ResponseWriter, r *http.Request, ws *websocket.Conn)
// Run the server.
// This is called from the generated main file.
-func Run(address string, port int) {
- routePath := path.Join(BasePath, "conf", "routes")
- MainRouter = NewRouter(routePath)
- MainTemplateLoader = NewTemplateLoader(ViewsPath, RevelTemplatePath)
+// If port is non-zero, use that. Else, read the port from app.conf.
+func Run(port int) {
+ address := Config.StringDefault("http.addr", "")
+ if port == 0 {
+ port = Config.IntDefault("http.port", 9000)
+ }
+
+ MainRouter = NewRouter(path.Join(BasePath, "conf", "routes"))
+ MainTemplateLoader = NewTemplateLoader(TemplatePaths)
// If desired (or by default), create a watcher for templates and routes.
// The watcher calls Refresh() on things on the first request.
if Config.BoolDefault("server.watcher", true) {
MainWatcher = NewWatcher()
- MainWatcher.Listen(MainTemplateLoader, ViewsPath, RevelTemplatePath)
- MainWatcher.Listen(MainRouter, routePath)
+ MainWatcher.Listen(MainTemplateLoader, MainTemplateLoader.paths...)
+ MainWatcher.Listen(MainRouter, MainRouter.path)
} else {
// Else, call refresh on them directly.
MainTemplateLoader.Refresh()
View
1  skeleton/conf/app.conf → skeleton/conf/app.conf.template
@@ -1,5 +1,6 @@
app.name={{ .AppName }}
app.secret={{ .Secret }}
+http.addr=
http.port=9000
[dev]
View
2  template.go
@@ -105,7 +105,7 @@ var (
}
)
-func NewTemplateLoader(paths ...string) *TemplateLoader {
+func NewTemplateLoader(paths []string) *TemplateLoader {
loader := &TemplateLoader{
paths: paths,
}
View
3  util.go
@@ -5,7 +5,6 @@ import (
"io"
"io/ioutil"
"net/url"
- "path"
"reflect"
"regexp"
"strings"
@@ -84,7 +83,7 @@ var mimeConfig *MergedConfig
// Load mime-types.conf on init.
func loadMimeConfig() {
var err error
- mimeConfig, err = LoadConfig(path.Join(RevelPath, "conf", "mime-types.conf"))
+ mimeConfig, err = LoadConfig("mime-types.conf")
if err != nil {
ERROR.Fatalln("Failed to load mime type config:", err)
}
View
11 util_test.go
@@ -1,6 +1,10 @@
package rev
-import "testing"
+import (
+ "path"
+ "path/filepath"
+ "testing"
+)
func TestContentTypeByFilename(t *testing.T) {
testCases := map[string]string{
@@ -10,6 +14,11 @@ func TestContentTypeByFilename(t *testing.T) {
"helloworld": "application/octet-stream",
"hello.world.c": "text/x-c; charset=utf-8",
}
+ ConfPaths = []string{path.Join(
+ findSrcPath(REVEL_IMPORT_PATH),
+ filepath.FromSlash(REVEL_IMPORT_PATH),
+ "conf"),
+ }
loadMimeConfig()
for filename, expected := range testCases {
actual := ContentTypeByFilename(filename)
Please sign in to comment.
Something went wrong with that request. Please try again.