Repository: caicloud/nirvana (go)

Issue:
`nirvana init` cannot be run outside GOPATH
<!-- Issue template; please answer the questions. -->

**Is this a BUG REPORT or FEATURE REQUEST?**:

/kind feature

**What happened**:

`nirvana init` cannot be run outside GOPATH:
```
$ nirvana init foo
FATAL 1027-18:47:01.921+08 init.go:50 | /tmp/foo is not in GOPATH
```

**What you expected to happen**:

`nirvana init` is able to create a project outside GOPATH.

**How to reproduce it (as minimally and precisely as possible)**:

run `nirvana init` outside GOPATH.

**Anything else we need to know?**:


Pull request description:
feat(*): make init/client command work with go module
<!--  Thanks for sending a pull request! See below for tips! -->

**What this PR does / why we need it**:

Continue #371, make `nirvana init/client` work with go module.

```console
$ nirvana init test2
INFO  1027-14:28:38.844+08 init.go:186 | Created project at /Users/bytedance/Downloads/test2

$ cat test2/go.mod
module test2

require (
	github.com/caicloud/nirvana master
)
```

```console
$ nirvana client pkg
INFO  1027-14:33:44.066+08 client.go:83 | Project root directory is /Users/bytedance/Downloads/test2
INFO  1027-14:33:44.069+08 client.go:119 | Generated golang client package test2/client
```

**Which issue(s) this PR is related to** *(optional, link to 3rd issue(s))*:

Fixes #374

<!-- 填在 Fixes，PR 合并就会关 issue。填在 Reference to 会关联 issue，不会联动关闭。-->

**Special notes for your reviewer**:

/cc @xpofei @knight42 

<!-- Please answer the following questions during the code freeze, and delete this line.
**Code freeze questions**

1. What causes this PR to not be merged before code freeze?
2. Why this PR is absolutely necessary for this version? Paste a screenshot of smoke testing docs if you could.
3. What's the effects after merging it?
4. Is there anyway we can skip this to not affect the overall process?
-->

**Release note**:
<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->

```release-note
Make `nirvana init/client` work with go module.
```

<!--  Thanks for sending a pull request! Here are some tips:

1. https://github.com/caicloud/engineering/blob/master/guidelines/review_conventions.md      <-- what is the review process looks like
2. https://github.com/caicloud/engineering/blob/master/guidelines/git_commit_conventions.md  <-- how to structure your git commit
3. https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md            <-- how to work with caicloud bot

Other tips:

If this is your first contribution, read our Getting Started guide https://github.com/caicloud/engineering/blob/master/guidelines/README.md
-->


Patch:
diff --git a/cmd/nirvana/client/client.go b/cmd/nirvana/client/client.go
index 0470c54..ec26db4 100644
--- a/cmd/nirvana/client/client.go
+++ b/cmd/nirvana/client/client.go
@@ -20,7 +20,9 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"path"
 	"path/filepath"
+	"strings"
 
 	"github.com/caicloud/nirvana/cmd/nirvana/buildutils"
 	"github.com/caicloud/nirvana/log"
@@ -90,6 +92,11 @@ func (o *clientOptions) Run(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
+	// handle go module project, pkg maybe just a directory name, eg: `client`
+	if !strings.HasPrefix(pkg, rootPkg) {
+		pkg = path.Join(rootPkg, pkg)
+	}
+
 	generator := golang.NewGenerator(config, definitions, o.Rest, pkg, rootPkg)
 	files, err := generator.Generate()
 	if err != nil {
diff --git a/utils/api/utils.go b/utils/api/utils.go
index 30ccb49..020df3f 100644
--- a/utils/api/utils.go
+++ b/utils/api/utils.go
@@ -61,9 +61,9 @@ func GenSwaggerPageData(root string, versions []string) ([]byte, error) {
   <head>
     <meta charset="UTF-8">
     <title>Swagger UI</title>
-    <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3.25.3/swagger-ui.css" >
-    <link rel="icon" type="image/png" href="https://unpkg.com/swagger-ui-dist@3.25.3/favicon-32x32.png" sizes="32x32" />
-    <link rel="icon" type="image/png" href="https://unpkg.com/swagger-ui-dist@3.25.3/favicon-16x16.png" sizes="16x16" />
+    <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3.36.0/swagger-ui.css" >
+    <link rel="icon" type="image/png" href="https://unpkg.com/swagger-ui-dist@3.36.0/favicon-32x32.png" sizes="32x32" />
+    <link rel="icon" type="image/png" href="https://unpkg.com/swagger-ui-dist@3.36.0/favicon-16x16.png" sizes="16x16" />
     <style>
       html
       {
@@ -86,8 +86,8 @@ func GenSwaggerPageData(root string, versions []string) ([]byte, error) {
   </head>
   <body>
     <div id="swagger-ui"></div>
-    <script src="https://unpkg.com/swagger-ui-dist@3.25.3/swagger-ui-bundle.js"> </script>
-    <script src="https://unpkg.com/swagger-ui-dist@3.25.3/swagger-ui-standalone-preset.js"> </script>
+    <script src="https://unpkg.com/swagger-ui-dist@3.36.0/swagger-ui-bundle.js"> </script>
+    <script src="https://unpkg.com/swagger-ui-dist@3.36.0/swagger-ui-standalone-preset.js"> </script>
     <script>
 	  // list of APIS
       var apis = [
diff --git a/utils/project/path.go b/utils/project/path.go
index a883928..87037d5 100644
--- a/utils/project/path.go
+++ b/utils/project/path.go
@@ -66,11 +66,19 @@ func GoPath(directory string) (goPath string, absPath string, err error) {
 
 // PackageForPath gets package path for a path.
 func PackageForPath(directory string) (string, error) {
-	goPath, absPath, err := GoPath(directory)
+	absPath, err := filepath.Abs(directory)
 	if err != nil {
 		return "", err
 	}
-	return filepath.ToSlash(strings.Trim(absPath[len(srcPath(goPath)):], string(os.PathSeparator))), nil
+	for i, path := range goSrcPaths {
+		if strings.HasPrefix(absPath, path) {
+			// in GOPATH
+			return filepath.ToSlash(strings.Trim(absPath[len(srcPath(goPaths[i])):], string(os.PathSeparator))), nil
+		}
+	}
+
+	// not in GOPATH, use the directory directly
+	return filepath.Base(directory), nil
 }
 
 // Subdirectories walkthroughs all subdirectories. The results contains itself.


Test patch:
diff --git a/utils/project/path_test.go b/utils/project/path_test.go
index 8c2b253..ca2fa74 100644
--- a/utils/project/path_test.go
+++ b/utils/project/path_test.go
@@ -21,13 +21,30 @@ import (
 )
 
 func TestPackageForPath(t *testing.T) {
-	directory := "./test1"
-	dest := "github.com/caicloud/nirvana/utils/project/test1"
-	packagePath, err := PackageForPath(directory)
-	if err != nil {
-		t.Error(err)
+	goPaths = []string{"/go"}
+	goSrcPaths = []string{"/go/src"}
+
+	tests := []struct {
+		name      string
+		directory string
+		wanted    string
+	}{
+		{
+			name:      "in GOPATH test",
+			directory: "/go/src/github.com/caicloud/test1",
+			wanted:    "github.com/caicloud/test1",
+		},
+		{
+			name:      "not in GOPATH test",
+			directory: "/usr/test",
+			wanted:    "test",
+		},
 	}
-	if packagePath != dest {
-		t.Error(packagePath)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got, _ := PackageForPath(tt.directory); got != tt.wanted {
+				t.Errorf("%s PackageForPath() = %v, want %v", tt.name, got, tt.wanted)
+			}
+		})
 	}
 }