From 010a36ab63d228c54dcb52da89005ac9fe6a13a7 Mon Sep 17 00:00:00 2001 From: Dave Brophy Date: Sat, 10 Mar 2018 00:06:31 +0100 Subject: [PATCH] Adds ImportName, ImportAlias --- README.md | 76 +++++++++++-- README.md.tpl | 10 ++ genjen/render.go | 45 ++++++++ jen/examples_test.go | 68 +++++++++-- jen/file.go | 71 +++++++++--- jen/generated.go | 266 +++++++++++++++++++++++++++++++++++++++++++ jen/jen.go | 29 +++-- 7 files changed, 519 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 640eb97..49070ba 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Output: ```go package main -import fmt "fmt" +import "fmt" func main() { fmt.Println("Hello, world") @@ -847,7 +847,7 @@ fmt.Printf("%#v", f) // Output: // package a // -// import unsafe "unsafe" +// import "unsafe" // // /* // #include @@ -919,7 +919,7 @@ if err != nil { ``` ### Anon -Anon adds an anonymous import: +Anon adds an anonymous import. ```go f := NewFile("c") @@ -934,6 +934,62 @@ fmt.Printf("%#v", f) // func init() {} ``` +### ImportName +ImportName provides the package name for a path. If specified, the alias will be omitted from the +import block. This is optional. If not specified, a sensible package name is used based on the path +and this is added as an alias in the import block. + +```go +f := NewFile("main") + +// package a should use name "a" +f.ImportName("github.com/foo/a", "a") + +// package b is not used in the code so will not be included +f.ImportName("github.com/foo/b", "b") + +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() +// } +``` + +### ImportAlias +ImportAlias provides the alias for a package path that should be used in the import block. + +```go +f := NewFile("main") + +// package a should be aliased to "b" +f.ImportAlias("github.com/foo/a", "b") + +// package c is not used in the code so will not be included +f.ImportAlias("github.com/foo/c", "c") + +f.Func().Id("main").Params().Block( + Qual("github.com/foo/a", "A").Call(), +) +fmt.Printf("%#v", f) + +// Output: +// package main +// +// import b "github.com/foo/a" +// +// func main() { +// b.A() +// } +``` + ### Comments PackageComment adds a comment to the top of the file, above the package keyword. @@ -961,22 +1017,22 @@ CgoPreamble adds a cgo preamble comment that is rendered directly before the "C" import. ### PackagePrefix -If you're worried about package aliases conflicting with local variable -names, you can set a prefix here. Package foo becomes {prefix}_foo. +If you're worried about generated package aliases conflicting with local variable names, you +can set a prefix here. Package foo becomes {prefix}_foo. ```go -f := NewFile("c") +f := NewFile("a") f.PackagePrefix = "pkg" f.Func().Id("main").Params().Block( - Qual("fmt", "Println").Call(), + Qual("b.c/d", "E").Call(), ) fmt.Printf("%#v", f) // Output: -// package c +// package a // -// import pkg_fmt "fmt" +// import pkg_d "b.c/d" // // func main() { -// pkg_fmt.Println() +// pkg_d.E() // } ``` diff --git a/README.md.tpl b/README.md.tpl index 937b424..9a6e097 100644 --- a/README.md.tpl +++ b/README.md.tpl @@ -391,6 +391,16 @@ the import is separated, and preceded by the preamble. {{ "ExampleFile_Anon" | example }} +### ImportName +{{ "File.ImportName" | doc }} + +{{ "ExampleFile_ImportName" | example }} + +### ImportAlias +{{ "File.ImportAlias" | doc }} + +{{ "ExampleFile_ImportAlias" | example }} + ### Comments {{ "File.PackageComment" | doc }} diff --git a/genjen/render.go b/genjen/render.go index 0630e57..8ac47f4 100644 --- a/genjen/render.go +++ b/genjen/render.go @@ -1,9 +1,14 @@ package main import ( + "go/build" "io" + "os/exec" "strings" + "fmt" + "path/filepath" + . "github.com/dave/jennifer/jen" ) @@ -202,6 +207,23 @@ func render(w io.Writer) error { ) } + packages, err := getStandardLibraryPackages() + if err != nil { + return err + } + /* + // PackageNameHints is a map containing hints for the names of all standard library packages + var PackageNameHints = map[string]string{ + ... + } + */ + file.Comment("PackageNameHints is a map containing hints for the names of all standard library packages") + file.Var().Id("PackageNameHints").Op("=").Map(String()).String().Values(DictFunc(func(d Dict) { + for path, name := range packages { + d[Lit(path)] = Lit(name) + } + })) + return file.Render(w) } @@ -254,3 +276,26 @@ func addFunctionAndGroupMethod( Return(Id("s")), ) } + +func getStandardLibraryPackages() (map[string]string, error) { + cmd := exec.Command("go", "list", "-f", "{{ .ImportPath }} {{ .Name }}", "./...") + cmd.Env = []string{ + fmt.Sprintf("GOPATH=%s", build.Default.GOPATH), + fmt.Sprintf("GOROOT=%s", build.Default.GOROOT), + } + cmd.Dir = filepath.Join(build.Default.GOROOT, "src") + b, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + all := strings.Split(strings.TrimSpace(string(b)), "\n") + + packages := map[string]string{} + for _, j := range all { + parts := strings.Split(j, " ") + path := parts[0] + name := parts[1] + packages[path] = name + } + return packages, nil +} diff --git a/jen/examples_test.go b/jen/examples_test.go index 24f0d90..f88dd78 100644 --- a/jen/examples_test.go +++ b/jen/examples_test.go @@ -8,6 +8,54 @@ import ( . "github.com/dave/jennifer/jen" ) +func ExampleFile_ImportName() { + f := NewFile("main") + + // package a should use name "a" + f.ImportName("github.com/foo/a", "a") + + // package b is not used in the code so will not be included + f.ImportName("github.com/foo/b", "b") + + 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") + + // package a should be aliased to "b" + f.ImportAlias("github.com/foo/a", "b") + + // package c is not used in the code so will not be included + f.ImportAlias("github.com/foo/c", "c") + + f.Func().Id("main").Params().Block( + Qual("github.com/foo/a", "A").Call(), + ) + fmt.Printf("%#v", f) + + // Output: + // package main + // + // import b "github.com/foo/a" + // + // func main() { + // b.A() + // } +} + func ExampleFile_CgoPreamble() { f := NewFile("a") f.CgoPreamble(`#include @@ -26,7 +74,7 @@ void myprint(char* s) { // Output: // package a // - // import unsafe "unsafe" + // import "unsafe" // // /* // #include @@ -82,7 +130,7 @@ func ExampleFile_CgoPreamble_no_preamble() { // // import ( // "C" - // fmt "fmt" + // "fmt" // ) // // func init() { @@ -132,7 +180,7 @@ func ExampleFile_CgoPreamble_no_preamble_anon() { // // import ( // "C" - // fmt "fmt" + // "fmt" // ) // // func init() { @@ -1298,7 +1346,7 @@ func ExampleId_remote() { // Output: // package main // - // import fmt "fmt" + // import "fmt" // // func main() { // fmt.Println("Hello, world") @@ -1329,7 +1377,7 @@ func ExampleNewFile() { // Output: // package main // - // import fmt "fmt" + // import "fmt" // // func main() { // fmt.Println("Hello, world") @@ -1379,18 +1427,18 @@ func ExampleFile_Anon() { } func ExampleFile_PackagePrefix() { - f := NewFile("c") + f := NewFile("a") f.PackagePrefix = "pkg" f.Func().Id("main").Params().Block( - Qual("fmt", "Println").Call(), + Qual("b.c/d", "E").Call(), ) fmt.Printf("%#v", f) // Output: - // package c + // package a // - // import pkg_fmt "fmt" + // import pkg_d "b.c/d" // // func main() { - // pkg_fmt.Println() + // pkg_d.E() // } } diff --git a/jen/file.go b/jen/file.go index 3ea9749..9e13e58 100644 --- a/jen/file.go +++ b/jen/file.go @@ -14,7 +14,8 @@ func NewFile(packageName string) *File { separator: "\n", }, name: packageName, - imports: map[string]string{}, + imports: map[string]importdef{}, + hints: map[string]importdef{}, } } @@ -27,7 +28,8 @@ func NewFilePath(packagePath string) *File { }, name: guessAlias(packagePath), path: packagePath, - imports: map[string]string{}, + imports: map[string]importdef{}, + hints: map[string]importdef{}, } } @@ -39,7 +41,8 @@ func NewFilePathName(packagePath, packageName string) *File { }, name: packageName, path: packagePath, - imports: map[string]string{}, + imports: map[string]importdef{}, + hints: map[string]importdef{}, } } @@ -49,15 +52,25 @@ type File struct { *Group name string path string - imports map[string]string + imports map[string]importdef + hints map[string]importdef comments []string headers []string cgoPreamble []string - // If you're worried about package aliases conflicting with local variable - // names, you can set a prefix here. Package foo becomes {prefix}_foo. + // If you're worried about generated package aliases conflicting with local variable names, you + // can set a prefix here. Package foo becomes {prefix}_foo. PackagePrefix string } +// importdef is used to differentiate packages where we know the package name from packages where the +// import is aliased. If alias == false, then name is the actual package name, and the import will be +// rendered without an alias. If used == false, the import has not been used in code yet and should be +// excluded from the import block. +type importdef struct { + name string + alias bool +} + // HeaderComment adds a comment to the top of the file, above any package // comments. A blank line is rendered below the header comments, ensuring // header comments are not included in the package doc. @@ -77,13 +90,25 @@ func (f *File) CgoPreamble(comment string) { f.cgoPreamble = append(f.cgoPreamble, comment) } -// Anon adds an anonymous import: +// Anon adds an anonymous import. func (f *File) Anon(paths ...string) { for _, p := range paths { - f.imports[p] = "_" + f.imports[p] = importdef{name: "_", alias: true} } } +// ImportName provides the package name for a path. If specified, the alias will be omitted from the +// import block. This is optional. If not specified, a sensible package name is used based on the path +// and this is added as an alias in the import block. +func (f *File) ImportName(path, name string) { + f.hints[path] = importdef{name: name, alias: false} +} + +// ImportAlias provides the alias for a package path that should be used in the import block. +func (f *File) ImportAlias(path, alias string) { + f.hints[path] = importdef{name: alias, alias: true} +} + func (f *File) isLocal(path string) bool { return f.path == path } @@ -113,7 +138,7 @@ func (f *File) isValidAlias(alias string) bool { } // the import alias is invalid if it's already been registered for _, v := range f.imports { - if alias == v { + if alias == v.name { return false } } @@ -127,14 +152,32 @@ func (f *File) register(path string) string { // so render will never be called. return "" } - if f.imports[path] != "" && f.imports[path] != "_" { - return f.imports[path] + + // if the path has been registered previously, simply return the name + def := f.imports[path] + if def.name != "" && def.name != "_" { + return def.name } + + // special case for "C" pseudo-package if path == "C" { - // special case for "C" pseudo-package - f.imports[path] = "C" + f.imports["C"] = importdef{name: "C", alias: false} return "C" } + + // look up the path in the list of provided package names and aliases by ImportName / ImportAlias + hint := f.hints[path] + if hint.name != "" { + f.imports[path] = importdef{name: hint.name, alias: hint.alias} + return hint.name + } + + // look up the path in the list of standard library packages, if found add and return the name + if PackageNameHints[path] != "" { + f.imports[path] = importdef{name: PackageNameHints[path], alias: false} + return PackageNameHints[path] + } + alias := guessAlias(path) unique := alias i := 0 @@ -145,7 +188,7 @@ func (f *File) register(path string) string { if f.PackagePrefix != "" { unique = f.PackagePrefix + "_" + unique } - f.imports[path] = unique + f.imports[path] = importdef{name: unique, alias: true} return unique } diff --git a/jen/generated.go b/jen/generated.go index 86e9b1d..efc3718 100644 --- a/jen/generated.go +++ b/jen/generated.go @@ -2225,3 +2225,269 @@ func (s *Statement) Range() *Statement { *s = append(*s, t) return s } + +// PackageNameHints is a map containing hints for the names of all standard library packages +var PackageNameHints = map[string]string{ + "archive/tar": "tar", + "archive/zip": "zip", + "bufio": "bufio", + "builtin": "builtin", + "bytes": "bytes", + "cmd/addr2line": "main", + "cmd/api": "main", + "cmd/asm": "main", + "cmd/asm/internal/arch": "arch", + "cmd/asm/internal/asm": "asm", + "cmd/asm/internal/flags": "flags", + "cmd/asm/internal/lex": "lex", + "cmd/buildid": "main", + "cmd/cgo": "main", + "cmd/compile": "main", + "cmd/compile/internal/amd64": "amd64", + "cmd/compile/internal/arm": "arm", + "cmd/compile/internal/arm64": "arm64", + "cmd/compile/internal/gc": "gc", + "cmd/compile/internal/mips": "mips", + "cmd/compile/internal/mips64": "mips64", + "cmd/compile/internal/ppc64": "ppc64", + "cmd/compile/internal/s390x": "s390x", + "cmd/compile/internal/ssa": "ssa", + "cmd/compile/internal/syntax": "syntax", + "cmd/compile/internal/test": "test", + "cmd/compile/internal/types": "types", + "cmd/compile/internal/x86": "x86", + "cmd/cover": "main", + "cmd/dist": "main", + "cmd/doc": "main", + "cmd/fix": "main", + "cmd/go": "main", + "cmd/go/internal/base": "base", + "cmd/go/internal/bug": "bug", + "cmd/go/internal/cache": "cache", + "cmd/go/internal/cfg": "cfg", + "cmd/go/internal/clean": "clean", + "cmd/go/internal/cmdflag": "cmdflag", + "cmd/go/internal/doc": "doc", + "cmd/go/internal/envcmd": "envcmd", + "cmd/go/internal/fix": "fix", + "cmd/go/internal/fmtcmd": "fmtcmd", + "cmd/go/internal/generate": "generate", + "cmd/go/internal/get": "get", + "cmd/go/internal/help": "help", + "cmd/go/internal/list": "list", + "cmd/go/internal/load": "load", + "cmd/go/internal/run": "run", + "cmd/go/internal/str": "str", + "cmd/go/internal/test": "test", + "cmd/go/internal/tool": "tool", + "cmd/go/internal/version": "version", + "cmd/go/internal/vet": "vet", + "cmd/go/internal/web": "web", + "cmd/go/internal/work": "work", + "cmd/gofmt": "main", + "cmd/internal/bio": "bio", + "cmd/internal/browser": "browser", + "cmd/internal/buildid": "buildid", + "cmd/internal/dwarf": "dwarf", + "cmd/internal/edit": "edit", + "cmd/internal/gcprog": "gcprog", + "cmd/internal/goobj": "goobj", + "cmd/internal/obj": "obj", + "cmd/internal/obj/arm": "arm", + "cmd/internal/obj/arm64": "arm64", + "cmd/internal/obj/mips": "mips", + "cmd/internal/obj/ppc64": "ppc64", + "cmd/internal/obj/s390x": "s390x", + "cmd/internal/obj/x86": "x86", + "cmd/internal/objabi": "objabi", + "cmd/internal/objfile": "objfile", + "cmd/internal/src": "src", + "cmd/internal/sys": "sys", + "cmd/internal/test2json": "test2json", + "cmd/link": "main", + "cmd/link/internal/amd64": "amd64", + "cmd/link/internal/arm": "arm", + "cmd/link/internal/arm64": "arm64", + "cmd/link/internal/ld": "ld", + "cmd/link/internal/loadelf": "loadelf", + "cmd/link/internal/loadmacho": "loadmacho", + "cmd/link/internal/loadpe": "loadpe", + "cmd/link/internal/mips": "mips", + "cmd/link/internal/mips64": "mips64", + "cmd/link/internal/objfile": "objfile", + "cmd/link/internal/ppc64": "ppc64", + "cmd/link/internal/s390x": "s390x", + "cmd/link/internal/sym": "sym", + "cmd/link/internal/x86": "x86", + "cmd/nm": "main", + "cmd/objdump": "main", + "cmd/pack": "main", + "cmd/pprof": "main", + "cmd/test2json": "main", + "cmd/trace": "main", + "cmd/vet": "main", + "cmd/vet/internal/cfg": "cfg", + "cmd/vet/internal/whitelist": "whitelist", + "compress/bzip2": "bzip2", + "compress/flate": "flate", + "compress/gzip": "gzip", + "compress/lzw": "lzw", + "compress/zlib": "zlib", + "container/heap": "heap", + "container/list": "list", + "container/ring": "ring", + "context": "context", + "crypto": "crypto", + "crypto/aes": "aes", + "crypto/cipher": "cipher", + "crypto/des": "des", + "crypto/dsa": "dsa", + "crypto/ecdsa": "ecdsa", + "crypto/elliptic": "elliptic", + "crypto/hmac": "hmac", + "crypto/internal/cipherhw": "cipherhw", + "crypto/md5": "md5", + "crypto/rand": "rand", + "crypto/rc4": "rc4", + "crypto/rsa": "rsa", + "crypto/sha1": "sha1", + "crypto/sha256": "sha256", + "crypto/sha512": "sha512", + "crypto/subtle": "subtle", + "crypto/tls": "tls", + "crypto/x509": "x509", + "crypto/x509/pkix": "pkix", + "database/sql": "sql", + "database/sql/driver": "driver", + "debug/dwarf": "dwarf", + "debug/elf": "elf", + "debug/gosym": "gosym", + "debug/macho": "macho", + "debug/pe": "pe", + "debug/plan9obj": "plan9obj", + "encoding": "encoding", + "encoding/ascii85": "ascii85", + "encoding/asn1": "asn1", + "encoding/base32": "base32", + "encoding/base64": "base64", + "encoding/binary": "binary", + "encoding/csv": "csv", + "encoding/gob": "gob", + "encoding/hex": "hex", + "encoding/json": "json", + "encoding/pem": "pem", + "encoding/xml": "xml", + "errors": "errors", + "expvar": "expvar", + "flag": "flag", + "fmt": "fmt", + "go/ast": "ast", + "go/build": "build", + "go/constant": "constant", + "go/doc": "doc", + "go/format": "format", + "go/importer": "importer", + "go/internal/gccgoimporter": "gccgoimporter", + "go/internal/gcimporter": "gcimporter", + "go/internal/srcimporter": "srcimporter", + "go/parser": "parser", + "go/printer": "printer", + "go/scanner": "scanner", + "go/token": "token", + "go/types": "types", + "hash": "hash", + "hash/adler32": "adler32", + "hash/crc32": "crc32", + "hash/crc64": "crc64", + "hash/fnv": "fnv", + "html": "html", + "html/template": "template", + "image": "image", + "image/color": "color", + "image/color/palette": "palette", + "image/draw": "draw", + "image/gif": "gif", + "image/internal/imageutil": "imageutil", + "image/jpeg": "jpeg", + "image/png": "png", + "index/suffixarray": "suffixarray", + "internal/cpu": "cpu", + "internal/nettrace": "nettrace", + "internal/poll": "poll", + "internal/race": "race", + "internal/singleflight": "singleflight", + "internal/syscall/windows": "windows", + "internal/syscall/windows/registry": "registry", + "internal/syscall/windows/sysdll": "sysdll", + "internal/testenv": "testenv", + "internal/testlog": "testlog", + "internal/trace": "trace", + "io": "io", + "io/ioutil": "ioutil", + "log": "log", + "log/syslog": "syslog", + "math": "math", + "math/big": "big", + "math/bits": "bits", + "math/cmplx": "cmplx", + "math/rand": "rand", + "mime": "mime", + "mime/multipart": "multipart", + "mime/quotedprintable": "quotedprintable", + "net": "net", + "net/http": "http", + "net/http/cgi": "cgi", + "net/http/cookiejar": "cookiejar", + "net/http/fcgi": "fcgi", + "net/http/httptest": "httptest", + "net/http/httptrace": "httptrace", + "net/http/httputil": "httputil", + "net/http/internal": "internal", + "net/http/pprof": "pprof", + "net/internal/socktest": "socktest", + "net/mail": "mail", + "net/rpc": "rpc", + "net/rpc/jsonrpc": "jsonrpc", + "net/smtp": "smtp", + "net/textproto": "textproto", + "net/url": "url", + "os": "os", + "os/exec": "exec", + "os/signal": "signal", + "os/signal/internal/pty": "pty", + "os/user": "user", + "path": "path", + "path/filepath": "filepath", + "plugin": "plugin", + "reflect": "reflect", + "regexp": "regexp", + "regexp/syntax": "syntax", + "runtime": "runtime", + "runtime/cgo": "cgo", + "runtime/debug": "debug", + "runtime/internal/atomic": "atomic", + "runtime/internal/sys": "sys", + "runtime/pprof": "pprof", + "runtime/pprof/internal/profile": "profile", + "runtime/race": "race", + "runtime/trace": "trace", + "sort": "sort", + "strconv": "strconv", + "strings": "strings", + "sync": "sync", + "sync/atomic": "atomic", + "syscall": "syscall", + "testing": "testing", + "testing/internal/testdeps": "testdeps", + "testing/iotest": "iotest", + "testing/quick": "quick", + "text/scanner": "scanner", + "text/tabwriter": "tabwriter", + "text/template": "template", + "text/template/parse": "parse", + "time": "time", + "unicode": "unicode", + "unicode/utf16": "utf16", + "unicode/utf8": "utf8", + "unsafe": "unsafe", +} diff --git a/jen/jen.go b/jen/jen.go index fa66105..367d77e 100644 --- a/jen/jen.go +++ b/jen/jen.go @@ -82,29 +82,31 @@ func (f *File) Render(w io.Writer) error { func (f *File) renderImports(source io.Writer) error { // Render the "C" import if it's been used in a `Qual`, `Anon` or if there's a preamble comment - hasCgo := f.imports["C"] != "" || len(f.cgoPreamble) > 0 + hasCgo := f.imports["C"].name != "" || len(f.cgoPreamble) > 0 // Only separate the import from the main imports block if there's a preamble separateCgo := hasCgo && len(f.cgoPreamble) > 0 - filtered := map[string]string{} - for path, alias := range f.imports { + filtered := map[string]importdef{} + for path, def := range f.imports { // filter out the "C" pseudo-package so it's not rendered in a block with the other // imports, but only if it is accompanied by a preamble comment if path == "C" && separateCgo { continue } - filtered[path] = alias + filtered[path] = def } if len(filtered) == 1 { - for path, alias := range filtered { - if path == "C" { - if _, err := fmt.Fprint(source, "import \"C\"\n\n"); err != nil { + for path, def := range filtered { + if def.alias && path != "C" { + // "C" package should be rendered without alias even when used as an anonymous import + // (e.g. should never have an underscore). + if _, err := fmt.Fprintf(source, "import %s %s\n\n", def.name, strconv.Quote(path)); err != nil { return err } } else { - if _, err := fmt.Fprintf(source, "import %s %s\n\n", alias, strconv.Quote(path)); err != nil { + if _, err := fmt.Fprintf(source, "import %s\n\n", strconv.Quote(path)); err != nil { return err } } @@ -121,13 +123,16 @@ func (f *File) renderImports(source io.Writer) error { } sort.Strings(paths) for _, path := range paths { - alias := filtered[path] - if path == "C" { - if _, err := fmt.Fprint(source, "\"C\"\n"); err != nil { + def := filtered[path] + if def.alias && path != "C" { + // "C" package should be rendered without alias even when used as an anonymous import + // (e.g. should never have an underscore). + if _, err := fmt.Fprintf(source, "%s %s\n", def.name, strconv.Quote(path)); err != nil { return err } + } else { - if _, err := fmt.Fprintf(source, "%s %s\n", alias, strconv.Quote(path)); err != nil { + if _, err := fmt.Fprintf(source, "%s\n", strconv.Quote(path)); err != nil { return err } }