Skip to content

Commit

Permalink
Add ImportNames and gennames command
Browse files Browse the repository at this point in the history
  • Loading branch information
dave committed Mar 10, 2018
1 parent 9be9266 commit db7de74
Show file tree
Hide file tree
Showing 13 changed files with 276 additions and 94 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ in your PR.
### Examples
Jennifer has a comprehensive suite of examples - see [godoc](https://godoc.org/github.com/dave/jennifer/jen#pkg-examples) for an index. Here's some examples of jennifer being used in the real-world:

* [genjen](https://github.com/dave/jennifer/blob/master/genjen/render.go) (which generates much of jennifer, using data in [data.go](https://github.com/dave/jennifer/blob/master/genjen/data.go))
* [genjen](genjen/render.go) (which generates much of jennifer, using data in [data.go](genjen/data.go))
* [frizz](https://github.com/frizz/frizz/blob/master/process/generate/structs.go)
* [zerogen](https://github.com/mrsinham/zerogen/blob/master/generator.go)
* [go-contentful-generator](https://github.com/nicolai86/go-contentful-generator)
Expand Down Expand Up @@ -970,6 +970,10 @@ fmt.Printf("%#v", f)
// }
```

### ImportNames
ImportNames allows multiple names to be imported as a map. Use the [gennames](gennames) command to
automatically generate a go file containing a map of a selection of package names.

### ImportAlias
ImportAlias provides the alias for a package path that should be used in the import block.

Expand Down
5 changes: 4 additions & 1 deletion README.md.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ in your PR.
### Examples
Jennifer has a comprehensive suite of examples - see [godoc](https://godoc.org/github.com/dave/jennifer/jen#pkg-examples) for an index. Here's some examples of jennifer being used in the real-world:

* [genjen](https://github.com/dave/jennifer/blob/master/genjen/render.go) (which generates much of jennifer, using data in [data.go](https://github.com/dave/jennifer/blob/master/genjen/data.go))
* [genjen](genjen/render.go) (which generates much of jennifer, using data in [data.go](genjen/data.go))
* [frizz](https://github.com/frizz/frizz/blob/master/process/generate/structs.go)
* [zerogen](https://github.com/mrsinham/zerogen/blob/master/generator.go)
* [go-contentful-generator](https://github.com/nicolai86/go-contentful-generator)
Expand Down Expand Up @@ -398,6 +398,9 @@ the import is separated, and preceded by the preamble.

{{ "ExampleFile_ImportName" | example }}

### ImportNames
{{ "File.ImportNames" | doc }}

### ImportAlias
{{ "File.ImportAlias" | doc }}

Expand Down
70 changes: 0 additions & 70 deletions genjen/hints.go

This file was deleted.

8 changes: 0 additions & 8 deletions genjen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,4 @@ func main() {
if err := ioutil.WriteFile("./jen/generated.go", buf.Bytes(), 0644); err != nil {
panic(err)
}

buf = &bytes.Buffer{}
if err := hints(buf); err != nil {
panic(err)
}
if err := ioutil.WriteFile("./jen/hints.go", buf.Bytes(), 0644); err != nil {
panic(err)
}
}
2 changes: 1 addition & 1 deletion genjen/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func render(w io.Writer) error {
file := NewFile("jen")

file.Comment("\tThis file is generated by genjen - do not edit!\n")
file.HeaderComment("This file is generated - do not edit.")
file.Line()

for _, b := range groups {
Expand Down
52 changes: 52 additions & 0 deletions gennames/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# gennames
For large projects, it may be useful to generate an index of package names for commonly used packages.
The index of names can be added to each generated file using `File.ImportNames`. The `gennames` command
is used internally to generate the list of standard library package names.

### Usage

```
Usage of gennames:
-filter string
Regex to filter paths (operates on full path including vendor directory) (default ".*")
-name string
Name of the variable to define (default "PackageNames")
-novendor
Exclude packages in vendor directories
-output string
Output filename to write (default "./package-names.go")
-package string
Package name in generated file (default "main")
-path string
Path to pass to go list command (default "all")
-standard
Use standard library packages
```

### Path
Supply a `path` to pass to the `go list` command. You may use the wildcard `/...` to recursively return
packages, but it's worth remembering that vendored packages are not returned by this method unless the
path itself is a vendored path. Use `all` to return all packages in your `GOPATH` (including vendored
packages), however remember this may take some time for a large `GOPATH`.

### Filter
Supply a regex `filter` to limit the packages that are returned by the `go list` command. The filter
operates on the full vendored package path (e.g. `github.com/foo/bar/vendor/github.com/baz/qux`), however
the package path added to the index is unvendored (e.g. `github.com/baz/qux`).

### Examples

```
gennames -filter "foo|bar"
```

Create a file named `package-names.go` with `package main` listing the names of all packages with paths
containing `foo` or `bar`.

```
gennames -output "foo/names.go" -package "foo" -path "github.com/foo/bar/vendor/..."
```

Create a file named `foo/names.go` with `package foo` listing the names of all packages that are vendored
inside `github.com/foo/bar`.

141 changes: 141 additions & 0 deletions gennames/hints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package main

import (
"fmt"
"go/build"
"io"
"os/exec"
"strings"

"regexp"

"path/filepath"

. "github.com/dave/jennifer/jen"
)

func hints(w io.Writer, pkg, name, goListPath, filter string, standard, novendor bool) error {

// notest

file := NewFile(pkg)

file.HeaderComment("This file is generated - do not edit.")
file.Line()

packages, err := getPackages(goListPath, filter, standard, novendor)
if err != nil {
return err
}
/*
// <name> contains package name hints
var <name> = map[string]string{
...
}
*/
file.Commentf("%s contains package name hints", name)
file.Var().Id(name).Op("=").Map(String()).String().Values(DictFunc(func(d Dict) {
for path, name := range packages {
d[Lit(path)] = Lit(name)
}
}))

return file.Render(w)
}

func getPackages(goListPath, filter string, standard, novendor bool) (map[string]string, error) {

// notest

r, err := regexp.Compile(filter)
if err != nil {
return nil, err
}

cmd := exec.Command("go", "list", "-e", "-f", "{{ .Standard }} {{ .ImportPath }} {{ .Name }}", goListPath)
cmd.Env = []string{
fmt.Sprintf("GOPATH=%s", build.Default.GOPATH),
fmt.Sprintf("GOROOT=%s", build.Default.GOROOT),
}
if standard {
cmd.Dir = filepath.Join(build.Default.GOROOT, "src")
} else {
cmd.Dir = filepath.Join(build.Default.GOPATH, "src")
}
b, err := cmd.Output()
if err != nil {
if x, ok := err.(*exec.ExitError); ok {
return nil, fmt.Errorf("go list command returned an error - %s: %s", err.Error(), string(x.Stderr))
}
return nil, fmt.Errorf("go list command returned an error: %s", err.Error())
}
all := strings.Split(strings.TrimSpace(string(b)), "\n")

packages := map[string]string{}
for _, j := range all {

parts := strings.Split(j, " ")

isStandard := parts[0] == "true"
if isStandard != standard {
continue
}

path := parts[1]
name := parts[2]

if novendor && hasVendor(path) {
continue
}

if name == "main" {
continue
}

if !r.MatchString(path) {
continue
}

path = unvendorPath(path)

if packages[path] != "" {
continue
}

packages[path] = name
}
return packages, nil
}

func unvendorPath(path string) string {
// notest
i, ok := findVendor(path)
if !ok {
return path
}
return path[i+len("vendor/"):]
}

// FindVendor looks for the last non-terminating "vendor" path element in the given import path.
// If there isn't one, FindVendor returns ok=false.
// Otherwise, FindVendor returns ok=true and the index of the "vendor".
// Copied from cmd/go/internal/load
func findVendor(path string) (index int, ok bool) {
// notest
// Two cases, depending on internal at start of string or not.
// The order matters: we must return the index of the final element,
// because the final one is where the effective import path starts.
switch {
case strings.Contains(path, "/vendor/"):
return strings.LastIndex(path, "/vendor/") + 1, true
case strings.HasPrefix(path, "vendor/"):
return 0, true
}
return 0, false
}

func hasVendor(path string) bool {
// notest
_, v := findVendor(path)
return v
}
29 changes: 29 additions & 0 deletions gennames/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"bytes"
"flag"
"io/ioutil"
"log"
)

func main() {
// notest

var out = flag.String("output", "./package-names.go", "Output filename to write")
var pkg = flag.String("package", "main", "Package name in generated file")
var name = flag.String("name", "PackageNames", "Name of the variable to define")
var filter = flag.String("filter", ".*", "Regex to filter paths (operates on full path including vendor directory)")
var standard = flag.Bool("standard", false, "Use standard library packages")
var novendor = flag.Bool("novendor", false, "Exclude packages in vendor directories")
var goListPath = flag.String("path", "all", "Path to pass to go list command")
flag.Parse()

buf := &bytes.Buffer{}
if err := hints(buf, *pkg, *name, *goListPath, *filter, *standard, *novendor); err != nil {
log.Fatal(err.Error())
}
if err := ioutil.WriteFile(*out, buf.Bytes(), 0644); err != nil {
log.Fatal(err.Error())
}
}
25 changes: 25 additions & 0 deletions jen/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,31 @@ func ExampleFile_ImportName() {
// }
}

func ExampleFile_ImportNames() {

// package a should use name "a", package b is not used in the code so will not be included
names := map[string]string{
"github.com/foo/a": "a",
"github.com/foo/b": "b",
}

f := NewFile("main")
f.ImportNames(names)
f.Func().Id("main").Params().Block(
Qual("github.com/foo/a", "A").Call(),
)
fmt.Printf("%#v", f)

// Output:
// package main
//
// import "github.com/foo/a"
//
// func main() {
// a.A()
// }
}

func ExampleFile_ImportAlias() {
f := NewFile("main")

Expand Down
Loading

0 comments on commit db7de74

Please sign in to comment.