diff --git a/iris/README.md b/iris/README.md index 96b7271c..957961bf 100644 --- a/iris/README.md +++ b/iris/README.md @@ -2,12 +2,14 @@ This package is the command line tool for [../](https://github.com/kataras/iris). + +[![Iris get command preview](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iriscmd.gif)](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iriscmd.gif) + + [![Iris help screen](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iris_cli_screen.png)](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iris_cli_screen.png) -[![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.9 ```sh go get -u github.com/kataras/iris/iris @@ -24,38 +26,20 @@ $ iris [command] [-flags] > Note that you must have $GOPATH/bin to your $PATH system/environment variable. -## create +## get -**The create command** creates for you a start project in a directory +**The get command** downloads, installs and runs a project based on a `prototype`, such as `basic`, `static` and `mongo` . + +> These projects are located [online](https://github.com/iris-contrib/examples/tree/master/AIO_examples) ```sh -iris create -t basic -d myprojects/iris1 +iris get mongo ``` -Will create the [basic](https://github.com/iris-contrib/iris-command-assets/tree/master/basic) sample package to the `$GOPATH/src/myprojects/iris1` directory and run the app. +Downloads the [mongo](https://github.com/iris-contrib/examples/tree/master/AIO_examples/mongo) sample protoype project to the `$GOPATH/src/github.com/iris-contrib/examples` directory(the iris cmd will open this folder to you, automatically) builds, runs and watch for source code changes (hot-reload) -```sh -iris create -t static -d myprojects/iris1 -``` - -Will create the [static](https://github.com/iris-contrib/iris-command-assets/tree/master/static) sample package to the `$GOPATH/src/myprojects/iris1` directory and run the app. - - -The default - -```sh -iris create -``` - -Will create the basic sample package to `$GOPATH/src/myiris` directory and run the app. - -```sh -iris create -d myproject -``` - -Will create the basic sample package to the `$GOPATH/src/myproject` folder and run the app. ## run diff --git a/iris/create.go b/iris/create.go deleted file mode 100644 index f83855e9..00000000 --- a/iris/create.go +++ /dev/null @@ -1,171 +0,0 @@ -package main - -import ( - "io/ioutil" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/kataras/cli" - "github.com/kataras/go-fs" - "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 + fs.PathSeparator + "iris-command-assets" + fs.PathSeparator - // packages should install with go get before create the package - packagesDependencies = []string{"github.com/iris-contrib/middleware/logger"} -) - -func isValidInstallDir(targetDir string) bool { - // https://github.com/kataras/iris/issues/237 - gopath := os.Getenv("GOPATH") - // remove the last ;/: for any case before the split - if idxLSep := strings.IndexByte(gopath, os.PathListSeparator); idxLSep == len(gopath)-1 { - gopath = gopath[0 : len(gopath)-2] - } - - // check if we have more than one gopath - gopaths := strings.Split(gopath, string(os.PathListSeparator)) - // the package MUST be installed only inside a valid gopath, if not then print an error to the user. - for _, gpath := range gopaths { - if strings.HasPrefix(targetDir, gpath+fs.PathSeparator) { - return true - } - } - return false -} - -func create(flags cli.Flags) (err error) { - - targetDir, err := filepath.Abs(flags.String("dir")) - if err != nil { - panic(err) - } - - if !isValidInstallDir(targetDir) { - printer.Dangerf("\nPlease make sure you are targeting a directory inside $GOPATH, type iris -h for help.") - return - } - - if !fs.DirectoryExists(packagesInstallDir) || !flags.Bool("offline") { - // install/update go dependencies at the same time downloading the zip from the github iris-contrib assets - finish := make(chan bool) - go func() { - go func() { - for _, source := range packagesDependencies { - gogetCmd := utils.CommandBuilder("go", "get", source) - if msg, err := gogetCmd.CombinedOutput(); err != nil { - panic("Unable to go get " + source + " please make sure you're connected to the internet.\nSolution: Remove your $GOPATH/src/github.com/iris-contrib/middleware folder and re-run the iris create\nReason:\n" + string(msg)) - } - } - finish <- true - - }() - - downloadPackages() - <-finish - }() - - <-finish - close(finish) - } - 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 := fs.Install(PackagesURL, packagesInstallDir, true) - 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 = fs.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 - fs.RemoveFile(installedDir[0 : len(installedDir)-1]) -} - -func createPackage(packageName string, targetDir string) error { - installTo := targetDir // os.Getenv("GOPATH") + fs.PathSeparator + "src" + fs.PathSeparator + targetDir - - packageDir := packagesInstallDir + fs.PathSeparator + packageName - err := fs.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 + fs.PathSeparator + "backend" + fs.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+"/", filepath.Base(targetDir)+"/", -1) - - err = ioutil.WriteFile(mainFile, []byte(output), 0777) - 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 += fs.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("." + fs.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 e1453b29..743ef584 100644 --- a/iris/doc.go +++ b/iris/doc.go @@ -2,17 +2,21 @@ package main // import "github.com/kataras/iris/iris" /* -go get -u github.com/kataras/iris/iris +- install iris command line tool +$ go get -u github.com/kataras/iris/iris -// create command -create an empty folder, open the command prompt/terminal there, type and press enter: +- get command +Downloads the mongo sample project prototype, open this folder to your explorer, +builds, runs and watches for source code changes (hot-reload). -iris create +$ iris get mongo -// run command -navigate to your app's directory and execute: +- run command +Runs and monitors for source code changes(hot-reload). + +$ cd mysites/site1 +$ iris run main.go -iris run main.go */ diff --git a/iris/fs.go b/iris/fs.go new file mode 100644 index 00000000..00ff8d19 --- /dev/null +++ b/iris/fs.go @@ -0,0 +1,84 @@ +package main + +import ( + "github.com/kataras/go-errors" + "os" + "path/filepath" + "strings" +) + +var workingDir string + +func getWorkingDir() string { + if workingDir == "" { + errUnableToGetWD := errors.New(Name + ": Unable to get working directory, %s") + // set the current working dir + d, err := os.Getwd() + if err != nil { + panic(errUnableToGetWD.Format(err)) + } + workingDir = d + } + return workingDir +} + +var goPath string + +// returns the (last) gopath+"/src/" +func getGoPath() string { + if goPath == "" { + errGoPathMissing := errors.New(Name + `: $GOPATH environment is missing. Please configure your $GOPATH. Reference: + https://github.com/golang/go/wiki/GOPATH`) + + // set the gopath + goPath = os.Getenv("GOPATH") + if goPath == "" { + // we should panic here + panic(errGoPathMissing) + } + + if idxLSep := strings.LastIndexByte(goPath, os.PathListSeparator); idxLSep == len(goPath)-1 { + // remove the last ';' or ':' in order to be safe to take the last correct path(if more than one ) + goPath = goPath[0 : len(goPath)-2] + } + if idxLSep := strings.IndexByte(goPath, os.PathListSeparator); idxLSep != -1 { + // we have more than one user-defined gopaths + goPath = goPath[idxLSep+1:] // take the last + } + } + + return join(goPath, "src") +} + +const pathsep = string(os.PathSeparator) + +// it just replaces / or double slashes with os.PathSeparator +// if second parameter receiver is true then it removes any ending slashes too +func fromslash(s string) string { + if len(s) == 0 { + return "" + } + + s = strings.Replace(s, "//", "/", -1) + s = strings.Replace(s, "/", pathsep, -1) + + return s +} + +func removeTrailingSlash(s string) string { + if s[len(s)-1] == os.PathSeparator { + s = s[0 : len(s)-2] + } + return s +} + +func toslash(paths ...string) string { + s := join(paths...) + s = strings.Replace(s, pathsep, "/", -1) + return removeTrailingSlash(s) +} + +// combine paths +func join(paths ...string) string { + return removeTrailingSlash(fromslash(filepath.Join(paths...))) +} diff --git a/iris/get.go b/iris/get.go new file mode 100644 index 00000000..2921d654 --- /dev/null +++ b/iris/get.go @@ -0,0 +1,133 @@ +package main + +import ( + "fmt" + "github.com/kataras/cli" + "github.com/kataras/go-fs" + "github.com/skratchdot/open-golang/open" + "os" + "os/exec" + "strings" +) + +// we introduce a project type, because I'm (not near future) planning dynamic inserting projects here by iris community +type project struct { + remote string // the gopath, used to go get 'gopath', if not exists in $GOPATH/src/'gopath' +} + +// first of all: we could fetch the AIO_examples and make the projects full dynamically, this would be perfect BUT not yet, +// for now lets make it dynamic via code, we want a third-party repo to be compatible also, not only iris-contrib/examples. +var ( + commonRepo = "github.com/iris-contrib/examples/AIO_examples/" + relativeMainFile = "main.go" + // the available projects/examples to build & run using this command line tool + projects = map[string]project{ + // the project type, passed on the get command : project.gopath & project.mainfile + "basic": project{remote: toslash(commonRepo, "basic", "backend")}, + "static": project{remote: toslash(commonRepo, "static", "backend")}, + "mongo": project{remote: toslash(commonRepo, "mongo", "backend")}, + } +) + +// dir returns the supposed local directory for this project +func (p project) dir() string { + return join(getGoPath(), p.remote) +} + +func (p project) mainfile() string { + return fromslash(p.dir() + "/" + relativeMainFile) +} + +func (p project) download() { + // first, check if the repo exists locally in gopath + if fs.DirectoryExists(p.dir()) { + return + } + app.Printf("Downloading... ") + finish := fs.ShowIndicator(cli.Output, false) + + defer func() { + + finish <- true //it's autoclosed so + }() + + // go get -u github.com/:owner/:repo + cmd := exec.Command("go", "get", p.remote) + cmd.Stdout = cli.Output + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Printf("Error while trying to download the package: %s\n Please make sure that you're connected to the internet.\n", err.Error()) + return + } + for i := 0; i < len("Building"); i++ { + app.Printf("\010\010\010") // remove the "loading" bars + } + +} + +func (p project) run() { + // in order to NOT re-write the import paths of all /backend/*.go, many things can go wrong, as they did before... + // it's better to just let the project exists in the examples folder and user can copy + // the source and change the import paths from there too + // so here just let run and watch it + mainFile := p.mainfile() + if !fs.DirectoryExists(mainFile) { // or file exists, same thing + p.download() + } + installedDir := p.dir() + app.Printf("Building %s\n\n", installedDir) + + // open this dir to the user, works with windows, osx, and other unix/linux & bsd* + // even if user already had the package, help him and open it in order to locate and change its content if needed + go open.Run(installedDir) + + // run and watch for source code changes + runAndWatch(mainFile) +} + +func buildGetCommand() *cli.Cmd { + var availabletypes []string + for k := range projects { + availabletypes = append(availabletypes, "'"+k+"'") + } + // comma separated of projects' map key + return cli.Command("get", "gets & runs a simple protoype-based project"). + Flag("type", + "basic", + // we take the os.Args in order to have access both via subcommand and when flag passed + "downloads, installs and runs a project based on a prototype. Currently, available types are: "+strings.Join(availabletypes, ",")). + Action(get) +} + +// iris get static +// iris get -t static +// iris get = iris get basic +// and all are happy +func get(flags cli.Flags) error { + // error and the end, not so idiomatic but needed here to make things easier + + if len(os.Args) >= 2 { // app name command name [and the package name] + + // set as the default value first + t := flags.String("type") // this never empty + + // now check if actually user passed a package name without the -t/-type + if len(os.Args) > 2 { + v := os.Args[2] + if !strings.HasPrefix(v, "-") { + t = v // change the default given with the actual user-defined 'subcommand form' + } + } + + for k, p := range projects { + if k == t { + p.run() + return nil + } + } + } + + err := errInvalidManualArgs.Format(strings.Join(os.Args[1:], ",")) + app.Printf(err.Error()) + return err +} diff --git a/iris/main.go b/iris/main.go index c72bd5e1..6a70ad70 100644 --- a/iris/main.go +++ b/iris/main.go @@ -1,59 +1,34 @@ package main import ( - "os" - - "github.com/iris-contrib/logger" "github.com/kataras/cli" "github.com/kataras/iris" ) -const ( - // Version of Iris command line tool - Version = "0.0.9" -) - var ( - app *cli.App - printer *logger.Logger - workingDir string + // Name the name of the cmd tool + Name = "Iris Command Line Tool" + app *cli.App + defaultInstallDir string ) func init() { - - // set the current working dir - if d, err := os.Getwd(); err != nil { - panic(err) - } else { - workingDir = d - } - // init the cli app - app = cli.NewApp("iris", "Command line tool for Iris web framework", Version) + app = cli.NewApp("iris", "Command line tool for Iris web framework", iris.Version) // 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", workingDir, "$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) - + app.Command(cli.Command("version", "\t prints your iris version"). + Action(func(cli.Flags) error { app.Printf("%s", app.Version); return nil })) // 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) + app.Command(buildGetCommand()) + app.Command(buildRunCommand()) - // init the logger - printer = logger.New(logger.DefaultConfig()) } func main() { // run the application - app.Run(func(f cli.Flags) error { + app.Run(func(cli.Flags) error { return nil }) } diff --git a/iris/run.go b/iris/run.go index 44e9e0f0..91afaf97 100644 --- a/iris/run.go +++ b/iris/run.go @@ -6,16 +6,28 @@ import ( "strings" "github.com/kataras/cli" + "github.com/kataras/go-errors" "github.com/kataras/rizla/rizla" ) -func runAndWatch(flags cli.Flags) error { +func buildRunCommand() *cli.Cmd { + return cli.Command("run", "runs and reload on source code changes, example: iris run main.go").Action(run) +} + +var errInvalidManualArgs = errors.New("Invalid arguments [%s], type -h to get assistant") + +func run(cli.Flags) error { if len(os.Args) <= 2 { - printer.Dangerf("Invalid arguments [%s], type -h to get assistant", strings.Join(os.Args, ",")) - os.Exit(-1) + err := errInvalidManualArgs.Format(strings.Join(os.Args, ",")) + app.Printf(err.Error()) // the return should print it too but do it for any case + return err } programPath := os.Args[2] + runAndWatch(programPath) + return nil +} +func runAndWatch(programPath string) { /* project := rizla.NewProject(programPath) project.Name = "IRIS" @@ -29,6 +41,4 @@ func runAndWatch(flags cli.Flags) error { // or just do that: rizla.DefaultDisableProgramRerunOutput = true // we don't want the banner to be shown after the first run rizla.Run(programPath) - - return nil } diff --git a/utils/installer.go b/utils/installer.go index d5f50498..cdb540a3 100644 --- a/utils/installer.go +++ b/utils/installer.go @@ -11,11 +11,11 @@ const ( ) 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 is the path which iris saves some assets came from the internet used mostly from iris control plugin (to download the html,css,js) AssetsDirectory = "" ) -// init just sets the iris path for assets, used in iris control plugin and for iris command line tool(create command) +// init just sets the iris path for assets, used in iris control plugin and GOPATH 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 := "" @@ -25,4 +25,5 @@ func init() { homepath = os.Getenv("HOME") } AssetsDirectory = homepath + PathSeparator + ".iris" + }