Fix and improve the iris cmd according to this: https://github.com/kataras/iris/issues/506

This commit is contained in:
Gerasimos Maropoulos 2016-10-23 07:20:39 +03:00
parent fc9cd0a8c2
commit 9958337e5d
8 changed files with 266 additions and 246 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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
*/

84
iris/fs.go Normal file
View File

@ -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...)))
}

133
iris/get.go Normal file
View File

@ -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
}

View File

@ -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 (
// Name the name of the cmd tool
Name = "Iris Command Line Tool"
app *cli.App
printer *logger.Logger
workingDir string
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
})
}

View File

@ -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
}

View File

@ -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"
}