From e9a47460002d52e41df537301760f9ca8a5d387d Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Mon, 20 Jun 2016 11:59:36 +0300 Subject: [PATCH] Update to rc.2 | NEW: iris run main.go https://github.com/kataras/iris/issues/192 Not tested on linux yet,(I do not have a linux station now). Post an issue if iris run main.go doesnt works as expected --- HISTORY.md | 10 +++ README.md | 4 +- iris.go | 2 +- iris/README.md | 27 ++++-- iris/create.go | 131 +++++++++++++++++++++++++++ iris/doc.go | 6 ++ iris/main.go | 166 ++++++++--------------------------- iris/run.go | 96 ++++++++++++++++++++ plugin/iriscontrol/plugin.go | 10 +-- utils/file.go | 59 ++++++++----- 10 files changed, 336 insertions(+), 175 deletions(-) create mode 100644 iris/create.go create mode 100644 iris/run.go diff --git a/HISTORY.md b/HISTORY.md index 1606a4ce..33a9ab8d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,15 @@ # History +## 3.0.0-rc.1 -> 3.0.0-rc.2 + +New: +- ` iris.MustUse/MustUseFunc` - registers middleware for all route parties, all subdomains and all routes. +- iris control plugin re-written, added real time browser request logger +- `websocket.OnError` - Add OnError to be able to catch internal errors from the connection +- [command line tool](https://github.com/kataras/iris/tree/master/iris) - `iris run main.go` runs, watch and reload on source code changes. As requested [here](https://github.com/kataras/iris/issues/192) + +Fixes: https://github.com/kataras/iris/issues/184 , https://github.com/kataras/iris/issues/175 . + ## 3.0.0-beta.3, 3.0.0-beta.4 -> 3.0.0-rc.1 This version took me many days because the whole framework's underline code is rewritten after many many many 'yoga'. Iris is not so small anymore, so I (tried) to organized it a little better. Note that, today, you can just go to [iris.go](https://github.com/kataras/iris/tree/master/iris.go) and [context.go](https://github.com/kataras/iris/tree/master/context/context.go) and look what functions you can use. You had some 'bugs' to subdomains, mail service, basic authentication and logger, these are fixed also, see below... diff --git a/README.md b/README.md index e8fc93bc..9cf476ae 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [Travis]: http://travis-ci.org/kataras/iris [License Widget]: https://img.shields.io/badge/license-Apache%20License%202.0-E91E63.svg?style=flat-square [License]: https://github.com/kataras/iris/blob/master/LICENSE -[Release Widget]: https://img.shields.io/badge/release-v3.0.0--rc.1-blue.svg?style=flat-square +[Release Widget]: https://img.shields.io/badge/release-v3.0.0--rc.2-blue.svg?style=flat-square [Release]: https://github.com/kataras/iris/releases [Gitter Widget]: https://img.shields.io/badge/chat-on%20gitter-00BCD4.svg?style=flat-square [Gitter]: https://gitter.im/kataras/iris @@ -124,7 +124,7 @@ Iris suggests you to use [this](https://github.com/gavv/httpexpect) new suite t Versioning ------------ -Current: **v3.0.0-rc.1** +Current: **v3.0.0-rc.2** > Iris is an active project diff --git a/iris.go b/iris.go index d2712b8b..f7d7bb51 100644 --- a/iris.go +++ b/iris.go @@ -69,7 +69,7 @@ import ( const ( // Version of the iris - Version = "3.0.0-rc.1" + Version = "3.0.0-rc.2" banner = ` _____ _ |_ _| (_) | | ____ _ ___ diff --git a/iris/README.md b/iris/README.md index 95bb06e8..2c405c3b 100644 --- a/iris/README.md +++ b/iris/README.md @@ -7,14 +7,14 @@ This package is the command line tool for [../](https://github.com/kataras/iris [![Iris installed screen](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iris_cli_screen2.png)](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iris_cli_screen2.png) ## Install -Current version: 0.0.4 +Current version: 0.0.5 ```sh go get -u github.com/kataras/iris/iris ``` -## Usage +# Usage ```sh @@ -24,7 +24,7 @@ $ iris [command] [-flags] > Note that you must have $GOPATH/bin to your $PATH system/environment variable. -## Create +## create **The create command** creates for you a start project in a directory @@ -57,15 +57,24 @@ iris create -d myproject Will create the basic sample package to the `$GOPATH/src/myproject` folder and run the app. +## run -## Version +**The run command** runs & reload on file changes your Iris station + +It's like ` go run ` but with directory watcher and re-run on .go file changes. + +```sh +iris run main.go +``` + +[![Iris CLI run showcase](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iris_command_line_tool_run_command.png)](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iris_command_line_tool_run_command.png) + + + +## version ```sh iris version ``` -Will print the current iris' installed version to your machine - -## TODO - -- [ ] Add more templates +Will print the current Iris' installed version to your machine diff --git a/iris/create.go b/iris/create.go new file mode 100644 index 00000000..230832b7 --- /dev/null +++ b/iris/create.go @@ -0,0 +1,131 @@ +package main + +import ( + "io/ioutil" + "os" + "runtime" + "strings" + + "github.com/kataras/cli" + "github.com/kataras/iris/utils" +) + +const ( + // PackagesURL the url to download all the packages + PackagesURL = "https://github.com/iris-contrib/iris-command-assets/archive/master.zip" + // PackagesExportedName the folder created after unzip + PackagesExportedName = "iris-command-assets-master" +) + +var ( + packagesInstallDir = utils.AssetsDirectory + utils.PathSeparator + "iris-command-assets" + utils.PathSeparator +) + +func create(flags cli.Flags) (err error) { + + if !utils.DirectoryExists(packagesInstallDir) || !flags.Bool("offline") { + downloadPackages() + } + + targetDir := flags.String("dir") + + // remove first and last / if any + if strings.HasPrefix(targetDir, "./") || strings.HasPrefix(targetDir, "."+utils.PathSeparator) { + targetDir = targetDir[2:] + } + if targetDir[len(targetDir)-1] == '/' { + targetDir = targetDir[0 : len(targetDir)-1] + } + // + + createPackage(flags.String("type"), targetDir) + return +} + +func downloadPackages() { + errMsg := "\nProblem while downloading the assets from the internet for the first time. Trace: %s" + + installedDir, err := utils.Install(PackagesURL, packagesInstallDir) + if err != nil { + printer.Dangerf(errMsg, err.Error()) + return + } + + // installedDir is the packagesInstallDir+PackagesExportedName, we will copy these contents to the parent, to the packagesInstallDir, because of import paths. + + err = utils.CopyDir(installedDir, packagesInstallDir) + if err != nil { + printer.Dangerf(errMsg, err.Error()) + return + } + + // we don't exit on errors here. + + // try to remove the unzipped folder + utils.RemoveFile(installedDir[0 : len(installedDir)-1]) +} + +func createPackage(packageName string, targetDir string) error { + installTo := os.Getenv("GOPATH") + utils.PathSeparator + "src" + utils.PathSeparator + targetDir + + packageDir := packagesInstallDir + utils.PathSeparator + packageName + err := utils.CopyDir(packageDir, installTo) + if err != nil { + printer.Dangerf("\nProblem while copying the %s package to the %s. Trace: %s", packageName, installTo, err.Error()) + return err + } + + // now replace main.go's 'github.com/iris-contrib/iris-command-assets/basic/' with targetDir + // hardcode all that, we don't have anything special and neither will do + targetDir = strings.Replace(targetDir, "\\", "/", -1) // for any case + mainFile := installTo + utils.PathSeparator + "backend" + utils.PathSeparator + "main.go" + + input, err := ioutil.ReadFile(mainFile) + if err != nil { + printer.Warningf("Error while preparing main file: %#v", err) + } + + output := strings.Replace(string(input), "github.com/iris-contrib/iris-command-assets/"+packageName+"/", targetDir+"/", -1) + + err = ioutil.WriteFile(mainFile, []byte(output), 0644) + if err != nil { + printer.Warningf("Error while preparing main file: %#v", err) + } + + printer.Infof("%s package was installed successfully [%s]", packageName, installTo) + + // build & run the server + + // go build + buildCmd := utils.CommandBuilder("go", "build") + if installTo[len(installTo)-1] != os.PathSeparator || installTo[len(installTo)-1] != '/' { + installTo += utils.PathSeparator + } + buildCmd.Dir = installTo + "backend" + buildCmd.Stderr = os.Stderr + err = buildCmd.Start() + if err != nil { + printer.Warningf("\n Failed to build the %s package. Trace: %s", packageName, err.Error()) + } + buildCmd.Wait() + print("\n\n") + + // run backend/backend(.exe) + executable := "backend" + if runtime.GOOS == "windows" { + executable += ".exe" + } + + runCmd := utils.CommandBuilder("." + utils.PathSeparator + executable) + runCmd.Dir = buildCmd.Dir + runCmd.Stdout = os.Stdout + runCmd.Stderr = os.Stderr + + err = runCmd.Start() + if err != nil { + printer.Warningf("\n Failed to run the %s package. Trace: %s", packageName, err.Error()) + } + runCmd.Wait() + + return err +} diff --git a/iris/doc.go b/iris/doc.go index 65c72185..ef6ba451 100644 --- a/iris/doc.go +++ b/iris/doc.go @@ -5,8 +5,14 @@ package main go get -u github.com/kataras/iris/iris +// create command create an empty folder, open the command prompt/terminal there, type and press enter: iris create + +// run command +navigate to your app's directory and execute: + +iris run main.go */ diff --git a/iris/main.go b/iris/main.go index 39dfc9eb..c63b8d13 100644 --- a/iris/main.go +++ b/iris/main.go @@ -3,155 +3,61 @@ package main import ( "os" + _ "syscall" + "strings" - "io/ioutil" - "runtime" - - "github.com/fatih/color" "github.com/kataras/cli" "github.com/kataras/iris" - "github.com/kataras/iris/utils" -) - -const ( - // PackagesURL the url to download all the packages - PackagesURL = "https://github.com/iris-contrib/iris-command-assets/archive/master.zip" - // PackagesExportedName the folder created after unzip - PackagesExportedName = "iris-command-assets-master" + "github.com/kataras/iris/config" + "github.com/kataras/iris/logger" ) var ( - app *cli.App - // SuccessPrint prints with a green color - SuccessPrint = color.New(color.FgGreen).Add(color.Bold).PrintfFunc() - // InfoPrint prints with the cyan color - InfoPrint = color.New(color.FgHiCyan).Add(color.Bold).PrintfFunc() - packagesInstallDir = os.Getenv("GOPATH") + utils.PathSeparator + "src" + utils.PathSeparator + "github.com" + utils.PathSeparator + "iris-contrib" + utils.PathSeparator + "iris-command-assets" + utils.PathSeparator + app *cli.App + printer *logger.Logger + workingDir string ) func init() { - app = cli.NewApp("iris", "Command line tool for Iris web framework", "0.0.4") + + // set the current working dir + if d, err := os.Getwd(); err != nil { + panic(err) + } else { + workingDir = d + } + + // defaultInstallDir is the default directory which the create will copy and run the package when finish downloading + // it's just the last path part of the workingDir + defaultInstallDir := workingDir[strings.LastIndexByte(workingDir, os.PathSeparator)+1:] + + // init the cli app + app = cli.NewApp("iris", "Command line tool for Iris web framework", "0.0.5") + // version command app.Command(cli.Command("version", "\t prints your iris version").Action(func(cli.Flags) error { app.Printf("%s", iris.Version); return nil })) + // create command/-/create.go createCmd := cli.Command("create", "create a project to a given directory"). Flag("offline", false, "set to true to disable the packages download on each create command"). - Flag("dir", "myiris", "$GOPATH/src/$dir the directory to install the sample package"). + Flag("dir", defaultInstallDir, "$GOPATH/src/$dir the directory to install the sample package"). Flag("type", "basic", "creates a project based on the -t package. Currently, available types are 'basic' & 'static'"). Action(create) + // run command/-/run.go + runAndWatchCmd := cli.Command("run", "runs and reload on source code changes, example: iris run main.go").Action(runAndWatch) + + // register the commands app.Command(createCmd) + app.Command(runAndWatchCmd) + + // init the logger + printer = logger.New(config.DefaultLogger()) } func main() { - app.Run(func(cli.Flags) error { return nil }) -} - -func create(flags cli.Flags) (err error) { - - if !utils.DirectoryExists(packagesInstallDir) || !flags.Bool("offline") { - downloadPackages() - } - - targetDir := flags.String("dir") - - // remove first and last / if any - if strings.HasPrefix(targetDir, "./") || strings.HasPrefix(targetDir, "."+utils.PathSeparator) { - targetDir = targetDir[2:] - } - if targetDir[len(targetDir)-1] == '/' { - targetDir = targetDir[0 : len(targetDir)-1] - } - // - - createPackage(flags.String("type"), targetDir) - return -} - -func downloadPackages() { - errMsg := "\nProblem while downloading the assets from the internet for the first time. Trace: %s" - - installedDir, err := utils.Install(PackagesURL, packagesInstallDir) - if err != nil { - app.Printf(errMsg, err.Error()) - return - } - - // installedDir is the packagesInstallDir+PackagesExportedName, we will copy these contents to the parent, to the packagesInstallDir, because of import paths. - - err = utils.CopyDir(installedDir, packagesInstallDir) - if err != nil { - app.Printf(errMsg, err.Error()) - return - } - - // we don't exit on errors here. - - // try to remove the unzipped folder - utils.RemoveFile(installedDir[0 : len(installedDir)-1]) -} - -func createPackage(packageName string, targetDir string) error { - installTo := os.Getenv("GOPATH") + utils.PathSeparator + "src" + utils.PathSeparator + targetDir - - packageDir := packagesInstallDir + utils.PathSeparator + packageName - err := utils.CopyDir(packageDir, installTo) - if err != nil { - app.Printf("\nProblem while copying the %s package to the %s. Trace: %s", packageName, installTo, err.Error()) - return err - } - - // now replace main.go's 'github.com/iris-contrib/iris-command-assets/basic/' with targetDir - // hardcode all that, we don't have anything special and neither will do - targetDir = strings.Replace(targetDir, "\\", "/", -1) // for any case - mainFile := installTo + utils.PathSeparator + "backend" + utils.PathSeparator + "main.go" - - input, err := ioutil.ReadFile(mainFile) - if err != nil { - app.Printf("Error while preparing main file: %#v", err) - } - - output := strings.Replace(string(input), "github.com/iris-contrib/iris-command-assets/"+packageName+"/", targetDir+"/", -1) - - err = ioutil.WriteFile(mainFile, []byte(output), 0644) - if err != nil { - app.Printf("Error while preparing main file: %#v", err) - } - - InfoPrint("%s package was installed successfully", packageName) - - // build & run the server - - // go build - buildCmd := utils.CommandBuilder("go", "build") - if installTo[len(installTo)-1] != os.PathSeparator || installTo[len(installTo)-1] != '/' { - installTo += utils.PathSeparator - } - buildCmd.Dir = installTo + "backend" - buildCmd.Stderr = os.Stderr - err = buildCmd.Start() - if err != nil { - app.Printf("\n Failed to build the %s package. Trace: %s", packageName, err.Error()) - } - buildCmd.Wait() - print("\n\n") - - // run backend/backend(.exe) - executable := "backend" - if runtime.GOOS == "windows" { - executable += ".exe" - } - - runCmd := utils.CommandBuilder("." + utils.PathSeparator + executable) - runCmd.Dir = buildCmd.Dir - runCmd.Stdout = os.Stdout - runCmd.Stderr = os.Stderr - - err = runCmd.Start() - if err != nil { - app.Printf("\n Failed to run the %s package. Trace: %s", packageName, err.Error()) - } - runCmd.Wait() - - return err + // run the application + app.Run(func(f cli.Flags) error { + return nil + }) } diff --git a/iris/run.go b/iris/run.go new file mode 100644 index 00000000..419a100b --- /dev/null +++ b/iris/run.go @@ -0,0 +1,96 @@ +package main + +import ( + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync/atomic" + + "github.com/kataras/cli" + "github.com/kataras/iris/errors" + "github.com/kataras/iris/utils" +) + +var ( + errInvalidArgs = errors.New("Invalid arguments [%s], type -h to get assistant") + errInvalidExt = errors.New("%s is not a go program") + errUnexpected = errors.New("Unexpected error!!! Please post an issue here: https://github.com/kataras/iris/issues") + goExt = ".go" +) + +func runAndWatch(flags cli.Flags) error { + if len(os.Args) <= 2 { + err := errInvalidArgs.Format(strings.Join(os.Args, ",")) + printer.Dangerf(err.Error()) + return err + } + programPath := "" + filenameCh := make(chan string) + + if len(os.Args) > 2 { // iris run main.go + programPath = os.Args[2] + if programPath[len(programPath)-1] == '/' { + programPath = programPath[0 : len(programPath)-1] + } + + if filepath.Ext(programPath) != goExt { + return errInvalidExt.Format(programPath) + } + } + // here(below), we don't return the error because the -help command doesn't help the user for these errors. + + // run the file watcher before all, because the user maybe has a go syntax error before the first run + utils.WatchDirectoryChanges(workingDir, func(fname string) { + if filepath.Ext(fname) == goExt { + filenameCh <- fname + } + + }, printer) + + // we don't use go build and run from the executable, for performance reasons, no need this is a development action already + goRun := utils.CommandBuilder("go", "run", programPath) + goRun.Dir = workingDir + goRun.Stdout = os.Stdout + goRun.Stderr = os.Stderr + if err := goRun.Start(); err != nil { + printer.Dangerf("\n [ERROR] Failed to run the %s iris program. Trace: %s", programPath, err.Error()) + return nil + } + + isWindows := runtime.GOOS == "windows" + defer func() { + printer.Dangerf("") + printer.Panic(errUnexpected) + }() + var times uint32 = 1 + for { + select { + case fname := <-filenameCh: + { + // it's not a warning but I like to use purple color for this message + printer.Warningf("\n/-%d-/ File '%s' changed, re-running...", atomic.LoadUint32(×), fname) + // force kill, sometimes runCmd.Process.Kill or Signal(os.Kill) doesn't kill the child of the go's go run command ( which is the iris program) + if isWindows { + utils.CommandBuilder("taskkill", "/F", "/T", "/PID", strconv.Itoa(goRun.Process.Pid)).Run() + } else { + utils.CommandBuilder("kill", "-INT", "-"+strconv.Itoa(goRun.Process.Pid)).Run() + } + + goRun = utils.CommandBuilder("go", "run", programPath) + goRun.Dir = workingDir + goRun.Stderr = os.Stderr + + if err := goRun.Start(); err != nil { + printer.Warningf("\n [ERROR ON RELOAD] Failed to run the %s iris program. Trace: %s", programPath, err.Error()) + + } else { + atomic.AddUint32(×, 1) + // don't print success on anything here because we may have error on iris itself, no need to print any message we are no spammers. + } + } + } + } + +} diff --git a/plugin/iriscontrol/plugin.go b/plugin/iriscontrol/plugin.go index 77b0caa0..b5e9863b 100644 --- a/plugin/iriscontrol/plugin.go +++ b/plugin/iriscontrol/plugin.go @@ -2,7 +2,6 @@ package iriscontrol import ( "os" - "runtime" "github.com/kataras/iris" "github.com/kataras/iris/utils" @@ -20,15 +19,8 @@ var ( // init just sets the assetsPath & current workingDir func init() { - homepath := "" - if runtime.GOOS == "windows" { - homepath = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") - } else { - homepath = os.Getenv("HOME") - } - assetsPath = homepath + utils.PathSeparator + ".iris" + utils.PathSeparator + "iris-control-assets" + utils.PathSeparator - workingDir, _ = os.Getwd() + assetsPath = utils.AssetsDirectory + utils.PathSeparator + "iris-control-assets" + utils.PathSeparator } func installAssets() { diff --git a/utils/file.go b/utils/file.go index 57b632cb..a6be74ce 100644 --- a/utils/file.go +++ b/utils/file.go @@ -9,8 +9,12 @@ import ( "net/http" "os" "path/filepath" + "runtime" "strings" "time" + + "github.com/fsnotify/fsnotify" + "github.com/kataras/iris/logger" ) const ( @@ -18,6 +22,23 @@ const ( ContentBINARY = "application/octet-stream" ) +var ( + // AssetsDirectory the path which iris saves some assets came from the internet ( used in iris control plugin (to download the html,css,js) and for iris command line tool to download the packages) + AssetsDirectory = "" +) + +// init just sets the iris path for assets, used in iris control plugin and for iris command line tool(create command) +// the AssetsDirectory path should be like: C:/users/kataras/.iris (for windows) and for linux you can imagine +func init() { + homepath := "" + if runtime.GOOS == "windows" { + homepath = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + } else { + homepath = os.Getenv("HOME") + } + AssetsDirectory = homepath + PathSeparator + ".iris" +} + // DirectoryExists returns true if a directory(or file) exists, otherwise false func DirectoryExists(dir string) bool { if _, err := os.Stat(dir); os.IsNotExist(err) { @@ -316,21 +337,14 @@ func GetParentDir(targetDirectory string) string { // 3-BSD License for package fsnotify/fsnotify // Copyright (c) 2012 The Go Authors. All rights reserved. // Copyright (c) 2012 fsnotify Authors. All rights reserved. - "github.com/fsnotify/fsnotify" - // - "github.com/kataras/iris/errors" - "github.com/kataras/iris/logger" +*/ -// WatchDirectoryChanges watches for directory changes and calls the 'evt' callback parameter -// unused after v2 but propably I will bring it back on v3 - -func WatchDirectoryChanges(rootPath string, evt func(filename string), logger ...*logger.Logger) { - watcher, err := fsnotify.NewWatcher() - - if err != nil { - if len(logger) > 0 { - errors.Printf(logger[0], err) - } +// WatchDirectoryChanges watches a directory and fires the callback with the changed name, receives a logger just to print with red letters any errors, no need for second callback. +func WatchDirectoryChanges(rootPath string, evt func(filename string), logger *logger.Logger) { + isWindows := runtime.GOOS == "windows" + watcher, werr := fsnotify.NewWatcher() + if werr != nil { + logger.Dangerf(werr.Error()) return } @@ -343,7 +357,7 @@ func WatchDirectoryChanges(rootPath string, evt func(filename string), logger .. if event.Op&fsnotify.Write == fsnotify.Write { //this is received two times, the last time is the real changed file, so i++ - if i%2 == 0 { + if i%2 == 0 || !isWindows { // this 'hack' works for windows but I dont know if works for linux too, we can wait for issue reports here. if time.Now().After(lastChange.Add(time.Duration(1) * time.Second)) { lastChange = time.Now() evt(event.Name) @@ -352,18 +366,15 @@ func WatchDirectoryChanges(rootPath string, evt func(filename string), logger .. } case err := <-watcher.Errors: - if len(logger) > 0 { - errors.Printf(logger[0], err) - } + logger.Dangerf(err.Error()) } } }() - err = watcher.Add(rootPath) - if err != nil { - if len(logger) > 0 { - errors.Printf(logger[0], err) - } + werr = watcher.Add(rootPath) + if werr != nil { + logger.Dangerf(werr.Error()) + } -}*/ +}