commit 860cff41ac4c4ad390acfcc3367aaa920cf681e5 Author: Justin Li Date: Tue Feb 25 22:49:42 2014 -0500 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..79e2ec8 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Revel command line tools + +Provides the `revel` command, used to create and run Revel apps. + diff --git a/revel/build.go b/revel/build.go new file mode 100644 index 0000000..3a5ac0f --- /dev/null +++ b/revel/build.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/revel/revel" + "github.com/revel/revel/harness" +) + +var cmdBuild = &Command{ + UsageLine: "build [import path] [target path]", + Short: "build a Revel application (e.g. for deployment)", + Long: ` +Build 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. + +WARNING: The target path will be completely deleted, if it already exists! + +For example: + + revel build github.com/revel/revel/samples/chat /tmp/chat +`, +} + +func init() { + cmdBuild.Run = buildApp +} + +func buildApp(args []string) { + if len(args) != 2 { + fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long) + return + } + + appImportPath, destPath := args[0], args[1] + if !revel.Initialized { + revel.Init("", appImportPath, "") + } + + // First, verify that it is either already empty or looks like a previous + // build (to avoid clobbering anything) + if exists(destPath) && !empty(destPath) && !exists(path.Join(destPath, "run.sh")) { + errorf("Abort: %s exists and does not look like a build directory.", destPath) + } + + os.RemoveAll(destPath) + os.MkdirAll(destPath, 0777) + + app, reverr := harness.Build() + panicOnError(reverr, "Failed to build") + + // Included are: + // - run scripts + // - binary + // - revel + // - app + + // Revel and the app are in a directory structure mirroring import path + srcPath := path.Join(destPath, "src") + destBinaryPath := path.Join(destPath, filepath.Base(app.BinaryPath)) + tmpRevelPath := path.Join(srcPath, filepath.FromSlash(revel.REVEL_IMPORT_PATH)) + mustCopyFile(destBinaryPath, app.BinaryPath) + mustChmod(destBinaryPath, 0755) + mustCopyDir(path.Join(tmpRevelPath, "conf"), path.Join(revel.RevelPath, "conf"), nil) + mustCopyDir(path.Join(tmpRevelPath, "templates"), path.Join(revel.RevelPath, "templates"), nil) + mustCopyDir(path.Join(srcPath, filepath.FromSlash(appImportPath)), revel.BasePath, nil) + + // Find all the modules used and copy them over. + config := revel.Config.Raw() + modulePaths := make(map[string]string) // import path => filesystem path + for _, section := range config.Sections() { + options, _ := config.SectionOptions(section) + for _, key := range options { + if !strings.HasPrefix(key, "module.") { + continue + } + moduleImportPath, _ := config.String(section, key) + if moduleImportPath == "" { + continue + } + modulePath, err := revel.ResolveImportPath(moduleImportPath) + if err != nil { + revel.ERROR.Fatalln("Failed to load module %s: %s", key[len("module."):], err) + } + modulePaths[moduleImportPath] = modulePath + } + } + for importPath, fsPath := range modulePaths { + mustCopyDir(path.Join(srcPath, importPath), fsPath, nil) + } + + tmplData, runShPath := map[string]interface{}{ + "BinName": filepath.Base(app.BinaryPath), + "ImportPath": appImportPath, + }, path.Join(destPath, "run.sh") + + mustRenderTemplate( + runShPath, + path.Join(revel.RevelPath, "revel", "package_run.sh.template"), + tmplData) + + mustChmod(runShPath, 0755) + + mustRenderTemplate( + path.Join(destPath, "run.bat"), + path.Join(revel.RevelPath, "revel", "package_run.bat.template"), + tmplData) +} diff --git a/revel/clean.go b/revel/clean.go new file mode 100644 index 0000000..15a43d3 --- /dev/null +++ b/revel/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: + + revel clean github.com/revel/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 + } +} diff --git a/revel/new.go b/revel/new.go new file mode 100644 index 0000000..7252226 --- /dev/null +++ b/revel/new.go @@ -0,0 +1,186 @@ +package main + +import ( + "bytes" + "fmt" + "go/build" + "math/rand" + "os" + "os/exec" + "path/filepath" + + "github.com/revel/revel" +) + +var cmdNew = &Command{ + UsageLine: "new [path] [skeleton]", + Short: "create a skeleton Revel application", + Long: ` +New creates a few files to get a new Revel application running quickly. + +It puts all of the files in the given import path, taking the final element in +the path to be the app name. + +Skeleton is an optional argument, provided as an import path + +For example: + + revel new import/path/helloworld + + revel new import/path/helloworld import/path/skeleton +`, +} + +func init() { + cmdNew.Run = newApp +} + +var ( + + // go related paths + gopath string + gocmd string + srcRoot string + + // revel related paths + revelPkg *build.Package + appPath string + appName string + basePath string + importPath string + skeletonPath string +) + +func newApp(args []string) { + // check for proper args by count + if len(args) == 0 { + errorf("No import path given.\nRun 'revel help new' for usage.\n") + } + if len(args) > 2 { + errorf("Too many arguments provided.\nRun 'revel help new' for usage.\n") + } + + // checking and setting go paths + initGoPaths() + + // checking and setting application + setApplicationPath(args) + + // checking and setting skeleton + setSkeletonPath(args) + + // copy files to new app directory + copyNewAppFiles() + + // goodbye world + fmt.Fprintln(os.Stdout, "Your application is ready:\n ", appPath) + fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run", importPath) +} + +const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +func generateSecret() string { + chars := make([]byte, 64) + for i := 0; i < 64; i++ { + chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))] + } + return string(chars) +} + +// lookup and set Go related variables +func initGoPaths() { + // lookup go path + gopath = build.Default.GOPATH + if gopath == "" { + errorf("Abort: GOPATH environment variable is not set. " + + "Please refer to http://golang.org/doc/code.html to configure your Go environment.") + } + + // set go src path + srcRoot = filepath.Join(filepath.SplitList(gopath)[0], "src") + + // check for go executable + var err error + gocmd, err = exec.LookPath("go") + if err != nil { + errorf("Go executable not found in PATH.") + } + +} + +func setApplicationPath(args []string) { + var err error + importPath = args[0] + if filepath.IsAbs(importPath) { + errorf("Abort: '%s' looks like a directory. Please provide a Go import path instead.", + importPath) + } + + _, err = build.Import(importPath, "", build.FindOnly) + if err == nil { + errorf("Abort: Import path %s already exists.\n", importPath) + } + + revelPkg, err = build.Import(revel.REVEL_IMPORT_PATH, "", build.FindOnly) + if err != nil { + errorf("Abort: Could not find Revel source code: %s\n", err) + } + + appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath)) + appName = filepath.Base(appPath) + basePath = filepath.ToSlash(filepath.Dir(importPath)) + + if basePath == "." { + // we need to remove the a single '.' when + // the app is in the $GOROOT/src directory + basePath = "" + } else { + // we need to append a '/' when the app is + // is a subdirectory such as $GOROOT/src/path/to/revelapp + basePath += "/" + } +} + +func setSkeletonPath(args []string) { + var err error + if len(args) == 2 { // user specified + skeletonName := args[1] + _, err = build.Import(skeletonName, "", build.FindOnly) + if err != nil { + // Execute "go get " + getCmd := exec.Command(gocmd, "get", "-d", skeletonName) + fmt.Println("Exec:", getCmd.Args) + getOutput, err := getCmd.CombinedOutput() + + // check getOutput for no buildible string + bpos := bytes.Index(getOutput, []byte("no buildable Go source files in")) + if err != nil && bpos == -1 { + errorf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, skeletonName) + } + } + // use the + skeletonPath = filepath.Join(srcRoot, skeletonName) + + } else { + // use the revel default + skeletonPath = filepath.Join(revelPkg.Dir, "skeleton") + } +} + +func copyNewAppFiles() { + var err error + err = os.MkdirAll(appPath, 0777) + panicOnError(err, "Failed to create directory "+appPath) + + mustCopyDir(appPath, skeletonPath, map[string]interface{}{ + // app.conf + "AppName": appName, + "BasePath": basePath, + "Secret": generateSecret(), + }) + + // Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore. + gitignore := ".gitignore" + mustCopyFile(filepath.Join(appPath, gitignore), filepath.Join(skeletonPath, gitignore)) + +} diff --git a/revel/package.go b/revel/package.go new file mode 100644 index 0000000..b909901 --- /dev/null +++ b/revel/package.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "github.com/revel/revel" + "io/ioutil" + "os" + "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: + + revel package github.com/revel/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] + revel.Init("", appImportPath, "") + + // Remove the archive if it already exists. + destFile := filepath.Base(revel.BasePath) + ".tar.gz" + os.Remove(destFile) + + // Collect stuff in a temp directory. + tmpDir, err := ioutil.TempDir("", filepath.Base(revel.BasePath)) + panicOnError(err, "Failed to get temp dir") + + buildApp([]string{args[0], tmpDir}) + + // Create the zip file. + archiveName := mustTarGzDir(destFile, tmpDir) + + fmt.Println("Your archive is ready:", archiveName) +} diff --git a/revel/package_run.bat.template b/revel/package_run.bat.template new file mode 100644 index 0000000..e73399b --- /dev/null +++ b/revel/package_run.bat.template @@ -0,0 +1,2 @@ +@echo off +{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode prod diff --git a/revel/package_run.sh.template b/revel/package_run.sh.template new file mode 100644 index 0000000..ac0e12a --- /dev/null +++ b/revel/package_run.sh.template @@ -0,0 +1,3 @@ +#!/bin/sh +SCRIPTPATH=$(cd "$(dirname "$0")"; pwd) +"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode prod diff --git a/revel/rev.go b/revel/rev.go new file mode 100644 index 0000000..91a60c3 --- /dev/null +++ b/revel/rev.go @@ -0,0 +1,129 @@ +// The command line tool for running Revel apps. +package main + +import ( + "flag" + "fmt" + "github.com/agtorre/gocolorize" + "io" + "math/rand" + "os" + "runtime" + "strings" + "text/template" + "time" +) + +// Cribbed from the genius organization of the "go" command. +type Command struct { + Run func(args []string) + UsageLine, Short, Long string +} + +func (cmd *Command) Name() string { + name := cmd.UsageLine + i := strings.Index(name, " ") + if i >= 0 { + name = name[:i] + } + return name +} + +var commands = []*Command{ + cmdNew, + cmdRun, + cmdBuild, + cmdPackage, + cmdClean, + cmdTest, +} + +func main() { + if runtime.GOOS == "windows" { + gocolorize.SetPlain(true) + } + fmt.Fprintf(os.Stdout, gocolorize.NewColor("blue").Paint(header)) + flag.Usage = func() { usage(1) } + flag.Parse() + args := flag.Args() + + if len(args) < 1 || args[0] == "help" { + if len(args) == 1 { + usage(0) + } + if len(args) > 1 { + for _, cmd := range commands { + if cmd.Name() == args[1] { + tmpl(os.Stdout, helpTemplate, cmd) + return + } + } + } + usage(2) + } + + // 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) + } + os.Exit(1) + } + }() + + for _, cmd := range commands { + if cmd.Name() == args[0] { + cmd.Run(args[1:]) + return + } + } + + errorf("unknown command %q\nRun 'revel help' for usage.\n", args[0]) +} + +func errorf(format string, args ...interface{}) { + // Ensure the user's command prompt starts on the next line. + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + fmt.Fprintf(os.Stderr, format, args...) + panic(LoggedError{}) // Panic instead of os.Exit so that deferred will run. +} + +const header = `~ +~ revel! http://revel.github.com +~ +` + +const usageTemplate = `usage: revel command [arguments] + +The commands are: +{{range .}} + {{.Name | printf "%-11s"}} {{.Short}}{{end}} + +Use "revel help [command]" for more information. +` + +var helpTemplate = `usage: revel {{.UsageLine}} +{{.Long}} +` + +func usage(exitCode int) { + tmpl(os.Stderr, usageTemplate, commands) + os.Exit(exitCode) +} + +func tmpl(w io.Writer, text string, data interface{}) { + t := template.New("top") + template.Must(t.Parse(text)) + if err := t.Execute(w, data); err != nil { + panic(err) + } +} + +func init() { + rand.Seed(time.Now().UnixNano()) +} diff --git a/revel/run.go b/revel/run.go new file mode 100644 index 0000000..0edc673 --- /dev/null +++ b/revel/run.go @@ -0,0 +1,73 @@ +package main + +import ( + "github.com/revel/revel" + "github.com/revel/revel/harness" + "strconv" +) + +var cmdRun = &Command{ + UsageLine: "run [import path] [run mode] [port]", + Short: "run a Revel application", + Long: ` +Run the Revel web application named by the given import path. + +For example, to run the chat room sample application: + + revel run github.com/revel/revel/samples/chat dev + +The run mode is used to select which set of app.conf configuration should +apply and may be used to determine logic in the application itself. + +Run mode defaults to "dev". + +You can set a port as an optional third parameter. For example: + + revel run github.com/revel/revel/samples/chat prod 8080`, +} + +func init() { + cmdRun.Run = runApp +} + +func runApp(args []string) { + if len(args) == 0 { + errorf("No import path given.\nRun 'revel help run' for usage.\n") + } + + // Determine the run mode. + mode := "dev" + if len(args) >= 2 { + mode = args[1] + } + + // Find and parse app.conf + revel.Init(mode, args[0], "") + revel.LoadMimeConfig() + + // Determine the override port, if any. + port := revel.HttpPort + if len(args) == 3 { + var err error + if port, err = strconv.Atoi(args[2]); err != nil { + errorf("Failed to parse port as integer: %s", args[2]) + } + } + + revel.INFO.Printf("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode) + revel.TRACE.Println("Base path:", revel.BasePath) + + // If the app is run in "watched" mode, use the harness to run it. + if revel.Config.BoolDefault("watch", true) && revel.Config.BoolDefault("watch.code", true) { + revel.HttpPort = port + harness.NewHarness().Run() // Never returns. + } + + // Else, just build and run the app. + app, err := harness.Build() + if err != nil { + errorf("Failed to build app: %s", err) + } + app.Port = port + app.Cmd().Run() +} diff --git a/revel/test.go b/revel/test.go new file mode 100644 index 0000000..33dfe62 --- /dev/null +++ b/revel/test.go @@ -0,0 +1,268 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/revel/revel" + "github.com/revel/revel/harness" + "github.com/revel/revel/modules/testrunner/app/controllers" + "io" + "io/ioutil" + "net/http" + "os" + "path" + "strings" + "time" +) + +var cmdTest = &Command{ + UsageLine: "test [import path] [run mode] [suite.method]", + Short: "run all tests from the command-line", + Long: ` +Run all tests for the Revel app named by the given import path. + +For example, to run the booking sample application's tests: + + revel test github.com/revel/revel/samples/booking dev + +The run mode is used to select which set of app.conf configuration should +apply and may be used to determine logic in the application itself. + +Run mode defaults to "dev". + +You can run a specific suite (and function) by specifying a third parameter. +For example, to run all of UserTest: + + revel test outspoken test UserTest + +or one of UserTest's methods: + + revel test outspoken test UserTest.Test1 +`, +} + +func init() { + cmdTest.Run = testApp +} + +func testApp(args []string) { + var err error + if len(args) == 0 { + errorf("No import path given.\nRun 'revel help test' for usage.\n") + } + + mode := "dev" + if len(args) >= 2 { + mode = args[1] + } + + // Find and parse app.conf + revel.Init(mode, args[0], "") + + // Ensure that the testrunner is loaded in this mode. + testRunnerFound := false + for _, module := range revel.Modules { + if module.ImportPath == "github.com/revel/revel/modules/testrunner" { + testRunnerFound = true + break + } + } + if !testRunnerFound { + errorf(`Error: The testrunner module is not running. + +You can add it to a run mode configuration with the following line: + + module.testrunner = github.com/revel/revel/modules/testrunner + +`) + } + + // Create a directory to hold the test result files. + resultPath := path.Join(revel.BasePath, "test-results") + if err = os.RemoveAll(resultPath); err != nil { + errorf("Failed to remove test result directory %s: %s", resultPath, err) + } + if err = os.Mkdir(resultPath, 0777); err != nil { + errorf("Failed to create test result directory %s: %s", resultPath, err) + } + + // Direct all the output into a file in the test-results directory. + file, err := os.OpenFile(path.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + errorf("Failed to create log file: %s", err) + } + + app, reverr := harness.Build() + if reverr != nil { + errorf("Error building: %s", reverr) + } + cmd := app.Cmd() + cmd.Stderr = io.MultiWriter(cmd.Stderr, file) + cmd.Stdout = io.MultiWriter(cmd.Stderr, file) + + // Start the app... + if err := cmd.Start(); err != nil { + errorf("%s", err) + } + defer cmd.Kill() + revel.INFO.Printf("Testing %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode) + + // Get a list of tests. + // Since this is the first request to the server, retry/sleep a couple times + // in case it hasn't finished starting up yet. + var ( + testSuites []controllers.TestSuiteDesc + resp *http.Response + baseUrl = fmt.Sprintf("http://127.0.0.1:%d", revel.HttpPort) + ) + for i := 0; ; i++ { + if resp, err = http.Get(baseUrl + "/@tests.list"); err == nil { + break + } + if i < 3 { + time.Sleep(3 * time.Second) + continue + } + errorf("Failed to request test list: %s", err) + } + defer resp.Body.Close() + json.NewDecoder(resp.Body).Decode(&testSuites) + + // If a specific TestSuite[.Method] is specified, only run that suite/test + if len(args) == 3 { + testSuites = filterTestSuites(testSuites, args[2]) + } + fmt.Printf("\n%d test suite%s to run.\n", len(testSuites), pluralize(len(testSuites), "", "s")) + fmt.Println() + + // Load the result template, which we execute for each suite. + module, _ := revel.ModuleByName("testrunner") + TemplateLoader := revel.NewTemplateLoader([]string{path.Join(module.Path, "app", "views")}) + if err := TemplateLoader.Refresh(); err != nil { + errorf("Failed to compile templates: %s", err) + } + resultTemplate, err := TemplateLoader.Template("TestRunner/SuiteResult.html") + if err != nil { + errorf("Failed to load suite result template: %s", err) + } + + // Run each suite. + var ( + overallSuccess = true + failedResults []controllers.TestSuiteResult + ) + for _, suite := range testSuites { + // Print the name of the suite we're running. + name := suite.Name + if len(name) > 22 { + name = name[:19] + "..." + } + fmt.Printf("%-22s", name) + + // Run every test. + startTime := time.Now() + suiteResult := controllers.TestSuiteResult{Name: suite.Name, Passed: true} + for _, test := range suite.Tests { + testUrl := baseUrl + "/@tests/" + suite.Name + "/" + test.Name + resp, err := http.Get(testUrl) + if err != nil { + errorf("Failed to fetch test result at url %s: %s", testUrl, err) + } + defer resp.Body.Close() + + var testResult controllers.TestResult + json.NewDecoder(resp.Body).Decode(&testResult) + if !testResult.Passed { + suiteResult.Passed = false + } + suiteResult.Results = append(suiteResult.Results, testResult) + } + overallSuccess = overallSuccess && suiteResult.Passed + + // Print result. (Just PASSED or FAILED, and the time taken) + suiteResultStr, suiteAlert := "PASSED", "" + if !suiteResult.Passed { + suiteResultStr, suiteAlert = "FAILED", "!" + failedResults = append(failedResults, suiteResult) + } + fmt.Printf("%8s%3s%6ds\n", suiteResultStr, suiteAlert, int(time.Since(startTime).Seconds())) + // Create the result HTML file. + suiteResultFilename := path.Join(resultPath, + fmt.Sprintf("%s.%s.html", suite.Name, strings.ToLower(suiteResultStr))) + suiteResultFile, err := os.Create(suiteResultFilename) + if err != nil { + errorf("Failed to create result file %s: %s", suiteResultFilename, err) + } + if err = resultTemplate.Render(suiteResultFile, suiteResult); err != nil { + errorf("Failed to render result template: %s", err) + } + } + + fmt.Println() + if overallSuccess { + writeResultFile(resultPath, "result.passed", "passed") + fmt.Println("All Tests Passed.") + } else { + for _, failedResult := range failedResults { + fmt.Printf("Failures:\n") + for _, result := range failedResult.Results { + if !result.Passed { + fmt.Printf("%s.%s\n", failedResult.Name, result.Name) + fmt.Printf("%s\n\n", result.ErrorSummary) + } + } + } + writeResultFile(resultPath, "result.failed", "failed") + errorf("Some tests failed. See file://%s for results.", resultPath) + } +} + +func writeResultFile(resultPath, name, content string) { + if err := ioutil.WriteFile(path.Join(resultPath, name), []byte(content), 0666); err != nil { + errorf("Failed to write result file %s: %s", path.Join(resultPath, name), err) + } +} + +func pluralize(num int, singular, plural string) string { + if num == 1 { + return singular + } + return plural +} + +// Filters test suites and individual tests to match +// the parsed command line parameter +func filterTestSuites(suites []controllers.TestSuiteDesc, suiteArgument string) []controllers.TestSuiteDesc { + var suiteName, testName string + argArray := strings.Split(suiteArgument, ".") + suiteName = argArray[0] + if suiteName == "" { + return suites + } + if len(argArray) == 2 { + testName = argArray[1] + } + for _, suite := range suites { + if suite.Name != suiteName { + continue + } + if testName == "" { + return []controllers.TestSuiteDesc{suite} + } + // Only run a particular test in a suite + for _, test := range suite.Tests { + if test.Name != testName { + continue + } + return []controllers.TestSuiteDesc{ + controllers.TestSuiteDesc{ + Name: suite.Name, + Tests: []controllers.TestDesc{test}, + }, + } + } + errorf("Couldn't find test %s in suite %s", testName, suiteName) + } + errorf("Couldn't find test suite %s", suiteName) + return nil +} diff --git a/revel/util.go b/revel/util.go new file mode 100644 index 0000000..f49221d --- /dev/null +++ b/revel/util.go @@ -0,0 +1,156 @@ +package main + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + "text/template" + + "github.com/revel/revel" +) + +// Use a wrapper to differentiate logged panics from unexpected ones. +type LoggedError struct{ error } + +func panicOnError(err error, msg string) { + if revErr, ok := err.(*revel.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()) +} + +func mustChmod(filename string, mode os.FileMode) { + err := os.Chmod(filename, mode) + panicOnError(err, fmt.Sprintf("Failed to chmod %d %q", mode, filename)) +} + +// 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 mustTarGzDir(destFilename, srcDir string) string { + zipFile, err := os.Create(destFilename) + panicOnError(err, "Failed to create archive") + defer zipFile.Close() + + gzipWriter := gzip.NewWriter(zipFile) + defer gzipWriter.Close() + + tarWriter := tar.NewWriter(gzipWriter) + defer tarWriter.Close() + + filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + srcFile, err := os.Open(srcPath) + panicOnError(err, "Failed to read source file") + defer srcFile.Close() + + err = tarWriter.WriteHeader(&tar.Header{ + Name: strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)), + Size: info.Size(), + Mode: int64(info.Mode()), + ModTime: info.ModTime(), + }) + panicOnError(err, "Failed to write tar entry header") + + _, err = io.Copy(tarWriter, srcFile) + panicOnError(err, "Failed to copy") + + return nil + }) + + return zipFile.Name() +} + +func exists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +// empty returns true if the given directory is empty. +// the directory must exist. +func empty(dirname string) bool { + dir, err := os.Open(dirname) + if err != nil { + errorf("error opening directory: %s", err) + } + defer dir.Close() + results, _ := dir.Readdir(1) + return len(results) == 0 +}