composeAndExecute command that loads and executes over arbitrary transformer set exported from a go plugin generated according to config file; test for plugin generation, loading, and execution; work on plugins README

This commit is contained in:
Ian Norden
2019-02-24 15:23:35 -06:00
parent fa4abe4e5c
commit 6c2d895023
116 changed files with 10554 additions and 454 deletions
+30
View File
@@ -0,0 +1,30 @@
*.iml
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
.DS_Store
.idea/
coverage.out
+14
View File
@@ -0,0 +1,14 @@
language: go
go:
- 1.x
notificaitons:
email:
recipients: dave@brophy.uk
on_failure: always
install:
- go get -u github.com/dave/courtney
- go get -t -v ./...
script:
- courtney -e
after_success:
- bash <(curl -s https://codecov.io/bash)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 David Brophy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1052
View File
File diff suppressed because it is too large Load Diff
+422
View File
@@ -0,0 +1,422 @@
[![Build Status](https://travis-ci.org/dave/jennifer.svg?branch=master)](https://travis-ci.org/dave/jennifer) [![Go Report Card](https://goreportcard.com/badge/github.com/dave/jennifer)](https://goreportcard.com/report/github.com/dave/jennifer) [![codecov](https://img.shields.io/badge/codecov-100%25-brightgreen.svg)](https://codecov.io/gh/dave/jennifer) ![stability-stable](https://img.shields.io/badge/stability-stable-brightgreen.svg) [![Sourcegraph](https://sourcegraph.com/github.com/dave/jennifer/jen/-/badge.svg)](https://sourcegraph.com/github.com/dave/jennifer?badge) <a href="https://patreon.com/davebrophy" title="Help with my hosting bills using Patreon"><img src="https://img.shields.io/badge/patreon-donate-yellow.svg" style="max-width:100%;"></a>
# Jennifer
Jennifer is a code generator for Go.
```go
package main
import (
"fmt"
. "github.com/dave/jennifer/jen"
)
func main() {{ "ExampleNewFile" | code }}
```
Output:
```go
{{ "ExampleNewFile" | output }}
```
### Install
```
go get -u github.com/dave/jennifer/jen
```
### Need help?
If you get stuck, have a question, would like a code review, or just want a
chat: I'm happy to help! Feel free to open an issue, email me or mention @dave
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](genjen/render.go) (which generates much of jennifer, using data in [data.go](genjen/data.go))
* [zerogen](https://github.com/mrsinham/zerogen/blob/master/generator.go)
* [go-contentful-generator](https://github.com/nicolai86/go-contentful-generator)
### Rendering
For testing, a File or Statement can be rendered with the fmt package
using the %#v verb.
{{ "ExampleCall_fmt" | example }}
This is not recommended for use in production because any error will cause a
panic. For production use, [File.Render](#render) or [File.Save](#save) are
preferred.
# Identifiers
**Identifiers** [Keywords](#keywords) [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) [Comments](#comments) [Helpers](#helpers) [Misc](#misc) [File](#file)
### Id
{{ "Id" | doc }}
{{ "ExampleId" | example }}
### Dot
{{ "Dot" | doc }}
{{ "ExampleDot" | example }}
### Qual
{{ "Qual[0]" | doc }}
{{ "ExampleQual" | example }}
{{ "Qual[1:4]" | doc }}
{{ "ExampleQual_file" | example }}
{{ "Qual[4:]" | doc }}
### List
{{ "List" | doc }}
{{ "ExampleList" | example }}
# Keywords
[Identifiers](#identifiers) **Keywords** [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) [Comments](#comments) [Helpers](#helpers) [Misc](#misc) [File](#file)
Simple keywords, predeclared identifiers and built-in functions are self
explanatory:
| Construct | Name |
| ---------------- | ---- |
| Keywords | Break, Chan, Const, Continue, Default, Defer, Else, Fallthrough, Func, Go, Goto, Range, Select, Type, Var |
| Functions | Append, Cap, Close, Complex, Copy, Delete, Imag, Len, Make, New, Panic, Print, Println, Real, Recover |
| Types | Bool, Byte, Complex64, Complex128, Error, Float32, Float64, Int, Int8, Int16, Int32, Int64, Rune, String, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr |
| Constants | True, False, Iota, Nil |
| Helpers | Err |
Built-in functions take a list of parameters and render them appropriately:
{{ "ExampleAppend_more" | example }}
Special cases for [If, For](#if-for), [Interface, Struct](#interface-struct), [Switch, Case](#switch-select), [Return](#return) and [Map](#map) are explained below.
# Operators
[Identifiers](#identifiers) [Keywords](#keywords) **Operators** [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) [Comments](#comments) [Helpers](#helpers) [Misc](#misc) [File](#file)
{{ "Op" | doc }}
{{ "ExampleOp" | example }}
{{ "ExampleOp_star" | example }}
{{ "ExampleOp_variadic" | example }}
{{ "ExampleOp_complex_conditions" | example }}
# Braces
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) **Braces** [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) [Comments](#comments) [Helpers](#helpers) [Misc](#misc) [File](#file)
Several methods render curly braces, summarized below:
| Name | Prefix | Separator | Example |
| ------------------------------ | ------------ | --------- | -------------------------------------|
| [Block](#block) | | `\n` | `func a() { ... }` or `if a { ... }` |
| [Interface](#interface-struct) | `interface` | `\n` | `interface { ... }` |
| [Struct](#interface-struct) | `struct` | `\n` | `struct { ... }` |
| [Values](#values) | | `,` | `[]int{1, 2}` or `A{B: "c"}` |
### Block
{{ "Block[:2]" | doc }}
{{ "ExampleBlock" | example }}
{{ "ExampleBlock_if" | example }}
{{ "Block[2:]" | doc }} [See example](#switch-select).
### Interface, Struct
Interface and Struct render the keyword followed by a statement list enclosed
by curly braces.
{{ "ExampleInterface_empty" | example }}
{{ "ExampleInterface" | example }}
{{ "ExampleStruct_empty" | example }}
{{ "ExampleStruct" | example }}
# Parentheses
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) [Braces](#braces) **Parentheses** [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) [Comments](#comments) [Helpers](#helpers) [Misc](#misc) [File](#file)
Several methods output parenthesis, summarized below:
| Name | Prefix | Separator | Example |
| ----------------- | ------ | --------- | --------------------------------- |
| [Call](#call) | | `,` | `fmt.Println(b, c)` |
| [Params](#params) | | `,` | `func (a *A) Foo(i int) { ... }` |
| [Defs](#defs) | | `\n` | `const ( ... )` |
| [Parens](#parens) | | | `[]byte(s)` or `a / (b + c)` |
| [Assert](#assert) | `.` | | `s, ok := i.(string)` |
### Call
{{ "Call" | doc }}
{{ "ExampleCall" | example }}
### Params
{{ "Params" | doc }}
{{ "ExampleParams" | example }}
### Defs
{{ "Defs" | doc }}
{{ "ExampleDefs" | example }}
### Parens
{{ "Parens" | doc }}
{{ "ExampleParens" | example }}
{{ "ExampleParens_order" | example }}
### Assert
{{ "Assert" | doc }}
{{ "ExampleAssert" | example }}
# Control flow
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) **Control flow** [Collections](#collections) [Literals](#literals) [Comments](#comments) [Helpers](#helpers) [Misc](#misc) [File](#file)
### If, For
If and For render the keyword followed by a semicolon separated list.
{{ "ExampleIf" | example }}
{{ "ExampleFor" | example }}
### Switch, Select
Switch, Select, Case and Block are used to build switch or select statements:
{{ "ExampleSwitch" | example }}
### Return
{{ "Return" | doc }}
{{ "ExampleReturn" | example }}
# Collections
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) **Collections** [Literals](#literals) [Comments](#comments) [Helpers](#helpers) [Misc](#misc) [File](#file)
### Map
{{ "Map" | doc }}
{{ "ExampleMap" | example }}
### Index
{{ "Index" | doc }}
{{ "ExampleIndex" | example }}
{{ "ExampleIndex_index" | example }}
{{ "ExampleIndex_empty" | example }}
### Values
{{ "Values" | doc }}
{{ "ExampleValues" | example }}
{{ "Dict" | doc }}
{{ "ExampleValues_dict_multiple" | example }}
{{ "ExampleValues_dict_composite" | example }}
{{ "DictFunc[0]" | doc }}
{{ "ExampleDictFunc" | example }}
Note: the items are ordered by key when rendered to ensure repeatable code.
# Literals
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) **Literals** [Comments](#comments) [Helpers](#helpers) [Misc](#misc) [File](#file)
### Lit
{{ "Lit" | doc }}
{{ "ExampleLit" | example }}
{{ "ExampleLit_float" | example }}
{{ "LitFunc[1:2]" | doc }}
{{ "ExampleLitFunc" | example }}
For the default constant types (bool, int, float64, string, complex128), Lit
will render the untyped constant.
| Code | Output |
| ------------- | ---------- |
| `Lit(true)` | `true` |
| `Lit(1)` | `1` |
| `Lit(1.0)` | `1.0` |
| `Lit("foo")` | `"foo"` |
| `Lit(0 + 1i)` | `(0 + 1i)` |
For all other built-in types (float32, int8, int16, int32, int64, uint, uint8,
uint16, uint32, uint64, uintptr, complex64), Lit will also render the type.
| Code | Output |
| ------------------------ | ------------------- |
| `Lit(float32(1))` | `float32(1)` |
| `Lit(int16(1))` | `int16(1)` |
| `Lit(uint8(0x1))` | `uint8(0x1)` |
| `Lit(complex64(0 + 1i))` | `complex64(0 + 1i)` |
The built-in alias types byte and rune need a special case. LitRune and LitByte
render rune and byte literals.
| Code | Output |
| ------------------------ | ----------- |
| `LitRune('x')` | `'x'` |
| `LitByte(byte(0x1))` | `byte(0x1)` |
# Comments
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) **Comments** [Helpers](#helpers) [Misc](#misc) [File](#file)
### Comment
{{ "Comment[:2]" | doc }}
{{ "ExampleComment" | example }}
{{ "ExampleComment_multiline" | example }}
{{ "Comment[2:]" | doc }}
{{ "ExampleComment_formatting_disabled" | example }}
### Commentf
{{ "Commentf[0]" | doc }}
{{ "ExampleCommentf" | example }}
# Helpers
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) [Comments](#comments) **Helpers** [Misc](#misc) [File](#file)
### Func methods
All constructs that accept a variadic list of items are paired with GroupFunc
functions that accept a func(*Group). Use for embedding logic.
{{ "ExampleValuesFunc" | example }}
{{ "ExampleBlockFunc" | example }}
### Add
{{ "Add" | doc }}
{{ "ExampleAdd" | example }}
{{ "ExampleAdd_var" | example }}
### Do
{{ "Do" | doc }}
{{ "ExampleDo" | example }}
# Misc
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) [Comments](#comments) [Helpers](#helpers) **Misc** [File](#file)
### Tag
{{ "Tag" | doc }}
{{ "ExampleTag" | example }}
Note: the items are ordered by key when rendered to ensure repeatable code.
### Null
{{ "Null" | doc }}
In lists, nil will produce the same effect.
{{ "ExampleNull_and_nil" | example }}
### Empty
{{ "Empty" | doc }}
{{ "ExampleEmpty" | example }}
### Line
{{ "Line" | doc }}
### Clone
Be careful when passing *Statement. Consider the following...
{{ "ExampleStatement_Clone_broken" | example }}
Id("a") returns a *Statement, which the Call() method appends to twice. To
avoid this, use Clone. {{ "Statement.Clone" | doc }}
{{ "ExampleStatement_Clone_fixed" | example }}
### Cgo
The cgo "C" pseudo-package is a special case, and always renders without a package alias. The
import can be added with `Qual`, `Anon` or by supplying a preamble. The preamble is added with
`File.CgoPreamble` which has the same semantics as [Comment](#comments). If a preamble is provided,
the import is separated, and preceded by the preamble.
{{ "ExampleFile_CgoPreamble" | example }}
# File
[Identifiers](#identifiers) [Keywords](#keywords) [Operators](#operators) [Braces](#braces) [Parentheses](#parentheses) [Control flow](#control-flow) [Collections](#collections) [Literals](#literals) [Comments](#comments) [Helpers](#helpers) [Misc](#misc) **File**
{{ "File" | doc }}
### NewFile
{{ "NewFile" | doc }}
### NewFilePath
{{ "NewFilePath" | doc }}
### NewFilePathName
{{ "NewFilePathName" | doc }}
{{ "ExampleNewFilePathName" | example }}
### Save
{{ "File.Save" | doc }}
### Render
{{ "File.Render" | doc }}
{{ "ExampleFile_Render" | example }}
### Anon
{{ "File.Anon" | doc }}
{{ "ExampleFile_Anon" | example }}
### ImportName
{{ "File.ImportName" | doc }}
{{ "ExampleFile_ImportName" | example }}
### ImportNames
{{ "File.ImportNames" | doc }}
### ImportAlias
{{ "File.ImportAlias" | doc }}
{{ "ExampleFile_ImportAlias" | example }}
### Comments
{{ "File.PackageComment" | doc }}
{{ "File.HeaderComment" | doc }}
{{ "File.CanonicalPath" | doc }}
{{ "ExampleFile_HeaderAndPackageComments" | example }}
{{ "File.CgoPreamble" | doc }}
### PackagePrefix
{{ "File.PackagePrefix" | doc }}
{{ "ExampleFile_PackagePrefix" | example }}
+307
View File
@@ -0,0 +1,307 @@
package main
var keywords = []string{"break", "default", "func", "select", "chan", "else", "const", "fallthrough", "type", "continue", "var", "goto", "defer", "go", "range"}
var identifiers = []string{"bool", "byte", "complex64", "complex128", "error", "float32", "float64", "int", "int8", "int16", "int32", "int64", "rune", "string", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "true", "false", "iota", "nil", "err"}
var groups = []struct {
name string // name of the function / method
comment string // comment appended to name
variadic bool // is the parameter variadic?
opening string // opening token
closing string // closing token
separator string // separator token
multi bool // items are always on multiple lines
parameters []string // parameter names
preventFunc bool // prevent the fooFunc function/method
}{
{
name: "Parens",
comment: "renders a single item in parenthesis. Use for type conversion or to specify evaluation order.",
variadic: false,
opening: "(",
closing: ")",
separator: "",
parameters: []string{"item"},
},
{
name: "List",
comment: "renders a comma separated list. Use for multiple return functions.",
variadic: true,
opening: "",
closing: "",
separator: ",",
parameters: []string{"items"},
},
{
name: "Values",
comment: "renders a comma separated list enclosed by curly braces. Use for slice or composite literals.",
variadic: true,
opening: "{",
closing: "}",
separator: ",",
parameters: []string{"values"},
},
{
name: "Index",
comment: "renders a colon separated list enclosed by square brackets. Use for array / slice indexes and definitions.",
variadic: true,
opening: "[",
closing: "]",
separator: ":",
parameters: []string{"items"},
},
{
name: "Block",
comment: "renders a statement list enclosed by curly braces. Use for code blocks. A special case applies when used directly after Case or Default, where the braces are omitted. This allows use in switch and select statements.",
variadic: true,
opening: "{",
closing: "}",
multi: true,
parameters: []string{"statements"},
},
{
name: "Defs",
comment: "renders a statement list enclosed in parenthesis. Use for definition lists.",
variadic: true,
opening: "(",
closing: ")",
multi: true,
parameters: []string{"definitions"},
},
{
name: "Call",
comment: "renders a comma separated list enclosed by parenthesis. Use for function calls.",
variadic: true,
opening: "(",
closing: ")",
separator: ",",
parameters: []string{"params"},
},
{
name: "Params",
comment: "renders a comma separated list enclosed by parenthesis. Use for function parameters and method receivers.",
variadic: true,
opening: "(",
closing: ")",
separator: ",",
parameters: []string{"params"},
},
{
name: "Assert",
comment: "renders a period followed by a single item enclosed by parenthesis. Use for type assertions.",
variadic: false,
opening: ".(",
closing: ")",
separator: "",
parameters: []string{"typ"},
},
{
name: "Map",
comment: "renders the keyword followed by a single item enclosed by square brackets. Use for map definitions.",
variadic: false,
opening: "map[",
closing: "]",
separator: "",
parameters: []string{"typ"},
},
{
name: "If",
comment: "renders the keyword followed by a semicolon separated list.",
variadic: true,
opening: "if ",
closing: "",
separator: ";",
parameters: []string{"conditions"},
},
{
name: "Return",
comment: "renders the keyword followed by a comma separated list.",
variadic: true,
opening: "return ",
closing: "",
separator: ",",
parameters: []string{"results"},
},
{
name: "For",
comment: "renders the keyword followed by a semicolon separated list.",
variadic: true,
opening: "for ",
closing: "",
separator: ";",
parameters: []string{"conditions"},
},
{
name: "Switch",
comment: "renders the keyword followed by a semicolon separated list.",
variadic: true,
opening: "switch ",
closing: "",
separator: ";",
parameters: []string{"conditions"},
},
{
name: "Interface",
comment: "renders the keyword followed by a method list enclosed by curly braces.",
variadic: true,
opening: "interface{",
closing: "}",
multi: true,
parameters: []string{"methods"},
},
{
name: "Struct",
comment: "renders the keyword followed by a field list enclosed by curly braces.",
variadic: true,
opening: "struct{",
closing: "}",
multi: true,
parameters: []string{"fields"},
},
{
name: "Case",
comment: "renders the keyword followed by a comma separated list.",
variadic: true,
opening: "case ",
closing: ":",
separator: ",",
parameters: []string{"cases"},
},
{
name: "Append",
comment: "renders the append built-in function.",
variadic: true,
opening: "append(",
closing: ")",
separator: ",",
parameters: []string{"args"},
},
{
name: "Cap",
comment: "renders the cap built-in function.",
variadic: false,
opening: "cap(",
closing: ")",
separator: ",",
parameters: []string{"v"},
},
{
name: "Close",
comment: "renders the close built-in function.",
variadic: false,
opening: "close(",
closing: ")",
separator: ",",
parameters: []string{"c"},
},
{
name: "Complex",
comment: "renders the complex built-in function.",
variadic: false,
opening: "complex(",
closing: ")",
separator: ",",
parameters: []string{"r", "i"},
},
{
name: "Copy",
comment: "renders the copy built-in function.",
variadic: false,
opening: "copy(",
closing: ")",
separator: ",",
parameters: []string{"dst", "src"},
},
{
name: "Delete",
comment: "renders the delete built-in function.",
variadic: false,
opening: "delete(",
closing: ")",
separator: ",",
parameters: []string{"m", "key"},
},
{
name: "Imag",
comment: "renders the imag built-in function.",
variadic: false,
opening: "imag(",
closing: ")",
separator: ",",
parameters: []string{"c"},
},
{
name: "Len",
comment: "renders the len built-in function.",
variadic: false,
opening: "len(",
closing: ")",
separator: ",",
parameters: []string{"v"},
},
{
name: "Make",
comment: "renders the make built-in function. The final parameter of the make function is optional, so it is represented by a variadic parameter list.",
variadic: true,
opening: "make(",
closing: ")",
separator: ",",
parameters: []string{"args"},
preventFunc: true, // the underlying function is not variadic, so we prevent the MakeFunc variation
},
{
name: "New",
comment: "renders the new built-in function.",
variadic: false,
opening: "new(",
closing: ")",
separator: ",",
parameters: []string{"typ"},
},
{
name: "Panic",
comment: "renders the panic built-in function.",
variadic: false,
opening: "panic(",
closing: ")",
separator: ",",
parameters: []string{"v"},
},
{
name: "Print",
comment: "renders the print built-in function.",
variadic: true,
opening: "print(",
closing: ")",
separator: ",",
parameters: []string{"args"},
},
{
name: "Println",
comment: "renders the println built-in function.",
variadic: true,
opening: "println(",
closing: ")",
separator: ",",
parameters: []string{"args"},
},
{
name: "Real",
comment: "renders the real built-in function.",
variadic: false,
opening: "real(",
closing: ")",
separator: ",",
parameters: []string{"c"},
},
{
name: "Recover",
comment: "renders the recover built-in function.",
variadic: false,
opening: "recover(",
closing: ")",
separator: ",",
parameters: []string{},
},
}
+17
View File
@@ -0,0 +1,17 @@
package main
import (
"bytes"
"io/ioutil"
)
func main() {
// notest
buf := &bytes.Buffer{}
if err := render(buf); err != nil {
panic(err)
}
if err := ioutil.WriteFile("./jen/generated.go", buf.Bytes(), 0644); err != nil {
panic(err)
}
}
+260
View File
@@ -0,0 +1,260 @@
package main
import (
"io"
"strings"
. "github.com/dave/jennifer/jen"
)
func render(w io.Writer) error {
file := NewFile("jen")
file.HeaderComment("This file is generated - do not edit.")
file.Line()
for _, b := range groups {
b := b // b used in closures
comment := Commentf("%s %s", b.name, b.comment)
if b.variadic && len(b.parameters) > 1 {
panic("should not have variadic function with multiple params")
}
var variadic Code
if b.variadic {
variadic = Op("...")
}
var funcParams []Code
var callParams []Code
for _, name := range b.parameters {
funcParams = append(funcParams, Id(name).Add(variadic).Id("Code"))
callParams = append(callParams, Id(name).Add(variadic))
}
addFunctionAndGroupMethod(
file,
b.name,
comment,
funcParams,
callParams,
false,
)
/*
// <comment>
func (s *Statement) <name>(<funcParams>) *Statement {
g := &Group{
items: []Code{<paramNames>}|<paramNames[0]>,
name: "<name>",
open: "<opening>",
close: "<closing>",
separator: "<separator>",
multi: <multi>,
}
*s = append(*s, g)
return s
}
*/
file.Add(comment)
file.Func().Params(
Id("s").Op("*").Id("Statement"),
).Id(b.name).Params(
funcParams...,
).Op("*").Id("Statement").Block(
Id("g").Op(":=").Op("&").Id("Group").Values(Dict{
Id("items"): Do(func(s *Statement) {
if b.variadic {
s.Id(b.parameters[0])
} else {
s.Index().Id("Code").ValuesFunc(func(g *Group) {
for _, name := range b.parameters {
g.Id(name)
}
})
}
}),
Id("name"): Lit(strings.ToLower(b.name)),
Id("open"): Lit(b.opening),
Id("close"): Lit(b.closing),
Id("separator"): Lit(b.separator),
Id("multi"): Lit(b.multi),
}),
Op("*").Id("s").Op("=").Append(Op("*").Id("s"), Id("g")),
Return(Id("s")),
)
if b.variadic && !b.preventFunc {
funcName := b.name + "Func"
funcComment := Commentf("%sFunc %s", b.name, b.comment)
funcFuncParams := []Code{Id("f").Func().Params(Op("*").Id("Group"))}
funcCallParams := []Code{Id("f")}
addFunctionAndGroupMethod(
file,
funcName,
funcComment,
funcFuncParams,
funcCallParams,
false,
)
/*
// <funcComment>
func (s *Statement) <funcName>(f func(*Group)) *Statement {
g := &Group{
name: "<name>",
open: "<opening>",
close: "<closing>",
separator: "<separator>",
multi: <multi>,
}
f(g)
*s = append(*s, g)
return s
}
*/
file.Add(funcComment)
file.Func().Params(
Id("s").Op("*").Id("Statement"),
).Id(funcName).Params(
funcFuncParams...,
).Op("*").Id("Statement").Block(
Id("g").Op(":=").Op("&").Id("Group").Values(Dict{
Id("name"): Lit(strings.ToLower(b.name)),
Id("open"): Lit(b.opening),
Id("close"): Lit(b.closing),
Id("separator"): Lit(b.separator),
Id("multi"): Lit(b.multi),
}),
Id("f").Call(Id("g")),
Op("*").Id("s").Op("=").Append(Op("*").Id("s"), Id("g")),
Return(Id("s")),
)
}
}
type tkn struct {
token string
name string
tokenType string
tokenDesc string
}
tokens := []tkn{}
for _, v := range identifiers {
tokens = append(tokens, tkn{
token: v,
name: strings.ToUpper(v[:1]) + v[1:],
tokenType: "identifierToken",
tokenDesc: "identifier",
})
}
for _, v := range keywords {
tokens = append(tokens, tkn{
token: v,
name: strings.ToUpper(v[:1]) + v[1:],
tokenType: "keywordToken",
tokenDesc: "keyword",
})
}
for i, t := range tokens {
t := t // used in closures
comment := Commentf(
"%s renders the %s %s.",
t.name,
t.token,
t.tokenDesc,
)
addFunctionAndGroupMethod(
file,
t.name,
comment,
nil,
nil,
i != 0, // only enforce test coverage on one item
)
/*
// <comment>
func (s *Statement) <name>() *Statement {
t := token{
typ: <tokenType>,
content: "<token>",
}
*s = append(*s, t)
return s
}
*/
file.Add(comment)
file.Func().Params(
Id("s").Op("*").Id("Statement"),
).Id(t.name).Params().Op("*").Id("Statement").Block(
Do(func(s *Statement) {
if i != 0 {
// only enforce test coverage on one item
s.Comment("notest")
}
}),
Id("t").Op(":=").Id("token").Values(Dict{
Id("typ"): Id(t.tokenType),
Id("content"): Lit(t.token),
}),
Op("*").Id("s").Op("=").Append(Op("*").Id("s"), Id("t")),
Return(Id("s")),
)
}
return file.Render(w)
}
// For each method on *Statement, this generates a package level
// function and a method on *Group, both with the same name.
func addFunctionAndGroupMethod(
file *File,
name string,
comment *Statement,
funcParams []Code,
callParams []Code,
notest bool,
) {
/*
// <comment>
func <name>(<funcParams>) *Statement {
return newStatement().<name>(<callParams>)
}
*/
file.Add(comment)
file.Func().Id(name).Params(funcParams...).Op("*").Id("Statement").Block(
Do(func(s *Statement) {
if notest {
// only enforce test coverage on one item
s.Comment("notest")
}
}),
Return(Id("newStatement").Call().Dot(name).Call(callParams...)),
)
/*
// <comment>
func (g *Group) <name>(<funcParams>) *Statement {
s := <name>(<callParams>)
g.items = append(g.items, s)
return s
}
*/
file.Add(comment)
file.Func().Params(
Id("g").Op("*").Id("Group"),
).Id(name).Params(funcParams...).Op("*").Id("Statement").Block(
Do(func(s *Statement) {
if notest {
// only enforce test coverage on one item
s.Comment("notest")
}
}),
Id("s").Op(":=").Id(name).Params(callParams...),
Id("g").Dot("items").Op("=").Append(Id("g").Dot("items"), Id("s")),
Return(Id("s")),
)
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"io/ioutil"
"regexp"
"testing"
"bytes"
)
func TestRender(t *testing.T) {
buf := &bytes.Buffer{}
if err := render(buf); err != nil {
t.Fatal(err.Error())
}
generatedString := buf.String()
existingFilePath := "../jen/generated.go"
existingBytes, err := ioutil.ReadFile(existingFilePath)
if err != nil {
t.Fatal(err.Error())
}
existingString := string(existingBytes)
// The "goimports" tool will often re-order the imports, so this is a
// kludge to remove it before comparing. This is not ideal!
importsRegex := regexp.MustCompile(`(?ms:\nimport \(\n.*\n\)\n)`)
generatedString = importsRegex.ReplaceAllString(generatedString, "-")
existingString = importsRegex.ReplaceAllString(existingString, "-")
if generatedString != existingString {
t.Fatalf("Generated code is not what is present:\n%s", generatedString)
}
}
+52
View File
@@ -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
View File
@@ -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
View File
@@ -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())
}
}
+19
View File
@@ -0,0 +1,19 @@
package jen
// Add appends the provided items to the statement.
func Add(code ...Code) *Statement {
return newStatement().Add(code...)
}
// Add appends the provided items to the statement.
func (g *Group) Add(code ...Code) *Statement {
s := Add(code...)
g.items = append(g.items, s)
return s
}
// Add appends the provided items to the statement.
func (s *Statement) Add(code ...Code) *Statement {
*s = append(*s, code...)
return s
}
+108
View File
@@ -0,0 +1,108 @@
package jen
import (
"fmt"
"io"
"strings"
)
// Comment adds a comment. If the provided string contains a newline, the
// comment is formatted in multiline style. If the comment string starts
// with "//" or "/*", the automatic formatting is disabled and the string is
// rendered directly.
func Comment(str string) *Statement {
return newStatement().Comment(str)
}
// Comment adds a comment. If the provided string contains a newline, the
// comment is formatted in multiline style. If the comment string starts
// with "//" or "/*", the automatic formatting is disabled and the string is
// rendered directly.
func (g *Group) Comment(str string) *Statement {
s := Comment(str)
g.items = append(g.items, s)
return s
}
// Comment adds a comment. If the provided string contains a newline, the
// comment is formatted in multiline style. If the comment string starts
// with "//" or "/*", the automatic formatting is disabled and the string is
// rendered directly.
func (s *Statement) Comment(str string) *Statement {
c := comment{
comment: str,
}
*s = append(*s, c)
return s
}
// Commentf adds a comment, using a format string and a list of parameters. If
// the provided string contains a newline, the comment is formatted in
// multiline style. If the comment string starts with "//" or "/*", the
// automatic formatting is disabled and the string is rendered directly.
func Commentf(format string, a ...interface{}) *Statement {
return newStatement().Commentf(format, a...)
}
// Commentf adds a comment, using a format string and a list of parameters. If
// the provided string contains a newline, the comment is formatted in
// multiline style. If the comment string starts with "//" or "/*", the
// automatic formatting is disabled and the string is rendered directly.
func (g *Group) Commentf(format string, a ...interface{}) *Statement {
s := Commentf(format, a...)
g.items = append(g.items, s)
return s
}
// Commentf adds a comment, using a format string and a list of parameters. If
// the provided string contains a newline, the comment is formatted in
// multiline style. If the comment string starts with "//" or "/*", the
// automatic formatting is disabled and the string is rendered directly.
func (s *Statement) Commentf(format string, a ...interface{}) *Statement {
c := comment{
comment: fmt.Sprintf(format, a...),
}
*s = append(*s, c)
return s
}
type comment struct {
comment string
}
func (c comment) isNull(f *File) bool {
return false
}
func (c comment) render(f *File, w io.Writer, s *Statement) error {
if strings.HasPrefix(c.comment, "//") || strings.HasPrefix(c.comment, "/*") {
// automatic formatting disabled.
if _, err := w.Write([]byte(c.comment)); err != nil {
return err
}
return nil
}
if strings.Contains(c.comment, "\n") {
if _, err := w.Write([]byte("/*\n")); err != nil {
return err
}
} else {
if _, err := w.Write([]byte("// ")); err != nil {
return err
}
}
if _, err := w.Write([]byte(c.comment)); err != nil {
return err
}
if strings.Contains(c.comment, "\n") {
if !strings.HasSuffix(c.comment, "\n") {
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
}
if _, err := w.Write([]byte("*/")); err != nil {
return err
}
}
return nil
}
+61
View File
@@ -0,0 +1,61 @@
package jen
// Options specifies options for the Custom method
type Options struct {
Open string
Close string
Separator string
Multi bool
}
// Custom renders a customized statement list. Pass in options to specify multi-line, and tokens for open, close, separator.
func Custom(options Options, statements ...Code) *Statement {
return newStatement().Custom(options, statements...)
}
// Custom renders a customized statement list. Pass in options to specify multi-line, and tokens for open, close, separator.
func (g *Group) Custom(options Options, statements ...Code) *Statement {
s := Custom(options, statements...)
g.items = append(g.items, s)
return s
}
// Custom renders a customized statement list. Pass in options to specify multi-line, and tokens for open, close, separator.
func (s *Statement) Custom(options Options, statements ...Code) *Statement {
g := &Group{
close: options.Close,
items: statements,
multi: options.Multi,
name: "custom",
open: options.Open,
separator: options.Separator,
}
*s = append(*s, g)
return s
}
// CustomFunc renders a customized statement list. Pass in options to specify multi-line, and tokens for open, close, separator.
func CustomFunc(options Options, f func(*Group)) *Statement {
return newStatement().CustomFunc(options, f)
}
// CustomFunc renders a customized statement list. Pass in options to specify multi-line, and tokens for open, close, separator.
func (g *Group) CustomFunc(options Options, f func(*Group)) *Statement {
s := CustomFunc(options, f)
g.items = append(g.items, s)
return s
}
// CustomFunc renders a customized statement list. Pass in options to specify multi-line, and tokens for open, close, separator.
func (s *Statement) CustomFunc(options Options, f func(*Group)) *Statement {
g := &Group{
close: options.Close,
multi: options.Multi,
name: "custom",
open: options.Open,
separator: options.Separator,
}
f(g)
*s = append(*s, g)
return s
}
+81
View File
@@ -0,0 +1,81 @@
package jen
import (
"bytes"
"io"
"sort"
)
// Dict renders as key/value pairs. Use with Values for map or composite
// literals.
type Dict map[Code]Code
// DictFunc executes a func(Dict) to generate the value. Use with Values for
// map or composite literals.
func DictFunc(f func(Dict)) Dict {
d := Dict{}
f(d)
return d
}
func (d Dict) render(f *File, w io.Writer, s *Statement) error {
first := true
// must order keys to ensure repeatable source
type kv struct {
k Code
v Code
}
lookup := map[string]kv{}
keys := []string{}
for k, v := range d {
if k.isNull(f) || v.isNull(f) {
continue
}
buf := &bytes.Buffer{}
if err := k.render(f, buf, nil); err != nil {
return err
}
keys = append(keys, buf.String())
lookup[buf.String()] = kv{k: k, v: v}
}
sort.Strings(keys)
for _, key := range keys {
k := lookup[key].k
v := lookup[key].v
if first && len(keys) > 1 {
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
first = false
}
if err := k.render(f, w, nil); err != nil {
return err
}
if _, err := w.Write([]byte(":")); err != nil {
return err
}
if err := v.render(f, w, nil); err != nil {
return err
}
if len(keys) > 1 {
if _, err := w.Write([]byte(",\n")); err != nil {
return err
}
}
}
return nil
}
func (d Dict) isNull(f *File) bool {
if d == nil || len(d) == 0 {
return true
}
for k, v := range d {
if !k.isNull(f) && !v.isNull(f) {
// if any of the key/value pairs are both not null, the Dict is not
// null
return false
}
}
return true
}
+22
View File
@@ -0,0 +1,22 @@
package jen
// Do calls the provided function with the statement as a parameter. Use for
// embedding logic.
func Do(f func(*Statement)) *Statement {
return newStatement().Do(f)
}
// Do calls the provided function with the statement as a parameter. Use for
// embedding logic.
func (g *Group) Do(f func(*Statement)) *Statement {
s := Do(f)
g.items = append(g.items, s)
return s
}
// Do calls the provided function with the statement as a parameter. Use for
// embedding logic.
func (s *Statement) Do(f func(*Statement)) *Statement {
f(s)
return s
}
File diff suppressed because it is too large Load Diff
+262
View File
@@ -0,0 +1,262 @@
package jen
import (
"bytes"
"fmt"
"regexp"
"strings"
)
// NewFile Creates a new file, with the specified package name.
func NewFile(packageName string) *File {
return &File{
Group: &Group{
multi: true,
},
name: packageName,
imports: map[string]importdef{},
hints: map[string]importdef{},
}
}
// NewFilePath creates a new file while specifying the package path - the
// package name is inferred from the path.
func NewFilePath(packagePath string) *File {
return &File{
Group: &Group{
multi: true,
},
name: guessAlias(packagePath),
path: packagePath,
imports: map[string]importdef{},
hints: map[string]importdef{},
}
}
// NewFilePathName creates a new file with the specified package path and name.
func NewFilePathName(packagePath, packageName string) *File {
return &File{
Group: &Group{
multi: true,
},
name: packageName,
path: packagePath,
imports: map[string]importdef{},
hints: map[string]importdef{},
}
}
// File represents a single source file. Package imports are managed
// automaticaly by File.
type File struct {
*Group
name string
path string
imports map[string]importdef
hints map[string]importdef
comments []string
headers []string
cgoPreamble []string
// 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
// CanonicalPath adds a canonical import path annotation to the package clause.
CanonicalPath 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.
func (f *File) HeaderComment(comment string) {
f.headers = append(f.headers, comment)
}
// PackageComment adds a comment to the top of the file, above the package
// keyword.
func (f *File) PackageComment(comment string) {
f.comments = append(f.comments, comment)
}
// CgoPreamble adds a cgo preamble comment that is rendered directly before the "C" pseudo-package
// import.
func (f *File) CgoPreamble(comment string) {
f.cgoPreamble = append(f.cgoPreamble, comment)
}
// Anon adds an anonymous import.
func (f *File) Anon(paths ...string) {
for _, p := range paths {
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}
}
// 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.
func (f *File) ImportNames(names map[string]string) {
for path, name := range names {
f.hints[path] = importdef{name: name, alias: false}
}
}
// ImportAlias provides the alias for a package path that should be used in the import block. A
// period can be used to force a dot-import.
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
}
var reserved = []string{
/* keywords */
"break", "default", "func", "interface", "select", "case", "defer", "go", "map", "struct", "chan", "else", "goto", "package", "switch", "const", "fallthrough", "if", "range", "type", "continue", "for", "import", "return", "var",
/* predeclared */
"bool", "byte", "complex64", "complex128", "error", "float32", "float64", "int", "int8", "int16", "int32", "int64", "rune", "string", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "true", "false", "iota", "nil", "append", "cap", "close", "complex", "copy", "delete", "imag", "len", "make", "new", "panic", "print", "println", "real", "recover",
/* common variables */
"err",
}
func isReservedWord(alias string) bool {
for _, name := range reserved {
if alias == name {
return true
}
}
return false
}
func (f *File) isValidAlias(alias string) bool {
// multiple dot-imports are ok
if alias == "." {
return true
}
// the import alias is invalid if it's a reserved word
if isReservedWord(alias) {
return false
}
// the import alias is invalid if it's already been registered
for _, v := range f.imports {
if alias == v.name {
return false
}
}
return true
}
func (f *File) isDotImport(path string) bool {
if id, ok := f.hints[path]; ok {
return id.name == "." && id.alias
}
return false
}
func (f *File) register(path string) string {
if f.isLocal(path) {
// notest
// should never get here becasue in Qual the packageToken will be null,
// so render will never be called.
return ""
}
// 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" {
f.imports["C"] = importdef{name: "C", alias: false}
return "C"
}
var name string
var alias bool
if hint := f.hints[path]; hint.name != "" {
// look up the path in the list of provided package names and aliases by ImportName / ImportAlias
name = hint.name
alias = hint.alias
} else if standardLibraryHints[path] != "" {
// look up the path in the list of standard library packages
name = standardLibraryHints[path]
alias = false
} else {
// if a hint is not found for the package, guess the alias from the package path
name = guessAlias(path)
alias = true
}
// If the name is invalid or has been registered already, make it unique by appending a number
unique := name
i := 0
for !f.isValidAlias(unique) {
i++
unique = fmt.Sprintf("%s%d", name, i)
}
// If we've changed the name to make it unique, it should definitely be an alias
if unique != name {
alias = true
}
// Only add a prefix if the name is an alias
if f.PackagePrefix != "" && alias {
unique = f.PackagePrefix + "_" + unique
}
// Register the eventual name
f.imports[path] = importdef{name: unique, alias: alias}
return unique
}
// GoString renders the File for testing. Any error will cause a panic.
func (f *File) GoString() string {
buf := &bytes.Buffer{}
if err := f.Render(buf); err != nil {
panic(err)
}
return buf.String()
}
func guessAlias(path string) string {
alias := path
if strings.HasSuffix(alias, "/") {
// training slashes are usually tolerated, so we can get rid of one if
// it exists
alias = alias[:len(alias)-1]
}
if strings.Contains(alias, "/") {
// if the path contains a "/", use the last part
alias = alias[strings.LastIndex(alias, "/")+1:]
}
// alias should be lower case
alias = strings.ToLower(alias)
// alias should now only contain alphanumerics
importsRegex := regexp.MustCompile(`[^a-z0-9]`)
alias = importsRegex.ReplaceAllString(alias, "")
return alias
}
+48
View File
@@ -0,0 +1,48 @@
package jen
import (
"fmt"
"testing"
)
func TestGuessAlias(t *testing.T) {
data := map[string]string{
"A": "a",
"a": "a",
"a$": "a",
"a/b": "b",
"a/b/c": "c",
"a/b/c-d": "cd",
"a/b/c-d/": "cd",
"a.b": "ab",
"a/b.c": "bc",
"a/b-c.d": "bcd",
"a/bb-ccc.dddd": "bbcccdddd",
"a/foo-go": "foogo",
}
for path, expected := range data {
if guessAlias(path) != expected {
fmt.Printf("guessAlias test failed %s should return %s but got %s\n", path, expected, guessAlias(path))
t.Fail()
}
}
}
func TestValidAlias(t *testing.T) {
data := map[string]bool{
"a": true, // ok
"b": false, // already registered
"go": false, // keyword
"int": false, // predeclared
"err": false, // common name
}
f := NewFile("test")
f.register("b")
for alias, expected := range data {
if f.isValidAlias(alias) != expected {
fmt.Printf("isValidAlias test failed %s should return %t but got %t\n", alias, expected, f.isValidAlias(alias))
t.Fail()
}
}
}
+2274
View File
File diff suppressed because it is too large Load Diff
+808
View File
@@ -0,0 +1,808 @@
package jen_test
import (
"testing"
. "github.com/dave/jennifer/jen"
)
var gencases = []tc{
{
desc: `bool group`,
code: BlockFunc(func(g *Group) {
g.Bool()
}),
expect: `{
bool
}`,
},
{
desc: `recover func`,
code: Recover(),
expect: `recover()`,
},
{
desc: `recover statement`,
code: Null().Recover(),
expect: `recover()`,
},
{
desc: `recover group`,
code: BlockFunc(func(g *Group) {
g.Recover()
}),
expect: `{
recover()
}`,
},
{
desc: `real func`,
code: Real(Id("a")),
expect: `real(a)`,
},
{
desc: `real statement`,
code: Null().Real(Id("a")),
expect: `real(a)`,
},
{
desc: `real group`,
code: BlockFunc(func(g *Group) {
g.Real(Id("a"))
}),
expect: `{
real(a)
}`,
},
{
desc: `printlnfunc func`,
code: PrintlnFunc(func(g *Group) { g.Id("a") }),
expect: `println(a)`,
},
{
desc: `printlnfunc statement`,
code: Null().PrintlnFunc(func(g *Group) { g.Id("a") }),
expect: `println(a)`,
},
{
desc: `printlnfunc group`,
code: BlockFunc(func(bg *Group) {
bg.PrintlnFunc(func(pg *Group) { pg.Id("a") })
}),
expect: `{
println(a)
}`,
},
{
desc: `println func`,
code: Println(Id("a")),
expect: `println(a)`,
},
{
desc: `println statement`,
code: Null().Println(Id("a")),
expect: `println(a)`,
},
{
desc: `println group`,
code: BlockFunc(func(g *Group) {
g.Println(Id("a"))
}),
expect: `{
println(a)
}`,
},
{
desc: `printfunc func`,
code: PrintFunc(func(g *Group) { g.Id("a") }),
expect: `print(a)`,
},
{
desc: `printfunc statement`,
code: Null().PrintFunc(func(g *Group) { g.Id("a") }),
expect: `print(a)`,
},
{
desc: `printfunc group`,
code: BlockFunc(func(bg *Group) {
bg.PrintFunc(func(pg *Group) { pg.Id("a") })
}),
expect: `{
print(a)
}`,
},
{
desc: `print func`,
code: Print(Id("a")),
expect: `print(a)`,
},
{
desc: `print statement`,
code: Null().Print(Id("a")),
expect: `print(a)`,
},
{
desc: `print group`,
code: BlockFunc(func(g *Group) {
g.Print(Id("a"))
}),
expect: `{
print(a)
}`,
},
{
desc: `panic func`,
code: Panic(Id("a")),
expect: `panic(a)`,
},
{
desc: `panic statement`,
code: Null().Panic(Id("a")),
expect: `panic(a)`,
},
{
desc: `panic group`,
code: BlockFunc(func(g *Group) {
g.Panic(Id("a"))
}),
expect: `{
panic(a)
}`,
},
{
desc: `new func`,
code: New(Id("a")),
expect: `new(a)`,
},
{
desc: `new statement`,
code: Id("a").Op(":=").New(Id("a")),
expect: `a := new(a)`,
},
{
desc: `new group`,
code: BlockFunc(func(g *Group) {
g.New(Id("a"))
}),
expect: `{
new(a)
}`,
},
{
desc: `make func`,
code: Make(Id("a")),
expect: `make(a)`,
},
{
desc: `make statement`,
code: Id("a").Op(":=").Make(Id("a")),
expect: `a := make(a)`,
},
{
desc: `make group`,
code: BlockFunc(func(g *Group) {
g.Make(Id("a"))
}),
expect: `{
make(a)
}`,
},
{
desc: `len func`,
code: Len(Id("a")),
expect: `len(a)`,
},
{
desc: `len statement`,
code: Id("a").Op(":=").Len(Id("a")),
expect: `a := len(a)`,
},
{
desc: `len group`,
code: BlockFunc(func(g *Group) {
g.Len(Id("a"))
}),
expect: `{
len(a)
}`,
},
{
desc: `imag func`,
code: Imag(Id("a")),
expect: `imag(a)`,
},
{
desc: `imag statement`,
code: Id("a").Op(":=").Imag(Id("a")),
expect: `a := imag(a)`,
},
{
desc: `imag group`,
code: BlockFunc(func(g *Group) {
g.Imag(Id("a"))
}),
expect: `{
imag(a)
}`,
},
{
desc: `delete func`,
code: Delete(Id("a"), Id("b")),
expect: `delete(a, b)`,
},
{
desc: `delete statement`,
code: Null().Delete(Id("a"), Id("b")),
expect: `delete(a, b)`,
},
{
desc: `delete group`,
code: BlockFunc(func(g *Group) {
g.Delete(Id("a"), Id("b"))
}),
expect: `{
delete(a, b)
}`,
},
{
desc: `copy func`,
code: Copy(Id("a"), Id("b")),
expect: `copy(a, b)`,
},
{
desc: `copy statement`,
code: Id("a").Op(":=").Copy(Id("a"), Id("b")),
expect: `a := copy(a, b)`,
},
{
desc: `copy group`,
code: BlockFunc(func(g *Group) {
g.Copy(Id("a"), Id("b"))
}),
expect: `{
copy(a, b)
}`,
},
{
desc: `complex func`,
code: Complex(Id("a"), Id("b")),
expect: `complex(a, b)`,
},
{
desc: `complex statement`,
code: Id("a").Op(":=").Complex(Id("a"), Id("b")),
expect: `a := complex(a, b)`,
},
{
desc: `complex group`,
code: BlockFunc(func(g *Group) {
g.Complex(Id("a"), Id("b"))
}),
expect: `{
complex(a, b)
}`,
},
{
desc: `close group`,
code: BlockFunc(func(g *Group) { g.Close(Id("a")) }),
expect: `{
close(a)
}`,
},
{
desc: `cap func`,
code: Cap(Id("a")),
expect: `cap(a)`,
},
{
desc: `cap statement`,
code: Id("a").Op(":=").Cap(Id("b")),
expect: `a := cap(b)`,
},
{
desc: `cap group`,
code: BlockFunc(func(g *Group) {
g.Cap(Id("a"))
}),
expect: `{
cap(a)
}`,
},
{
desc: `append group`,
code: BlockFunc(func(g *Group) {
g.Append(Id("a"))
}),
expect: `{
append(a)
}`,
},
{
desc: `appendfunc statement`,
code: Id("a").Op("=").AppendFunc(func(ag *Group) { ag.Id("a") }),
expect: `a = append(a)`,
},
{
desc: `appendfunc func`,
code: AppendFunc(func(ag *Group) { ag.Id("a") }),
expect: `append(a)`,
},
{
desc: `appendfunc group`,
code: BlockFunc(func(bg *Group) {
bg.AppendFunc(func(ag *Group) { ag.Id("a") })
}),
expect: `{
append(a)
}`,
},
{
desc: `casefunc group`,
code: Switch().BlockFunc(func(g *Group) {
g.CaseFunc(func(g *Group) { g.Id("a") }).Block()
}),
expect: `switch {
case a:
}`,
},
{
desc: `case group`,
code: Switch().BlockFunc(func(g *Group) {
g.Case(Id("a")).Block()
}),
expect: `switch {
case a:
}`,
},
{
desc: `structfunc statement`,
code: Id("a").Op(":=").StructFunc(func(g *Group) {}).Values(),
expect: `a := struct{}{}`,
},
{
desc: `structfunc group`,
// Don't do this! ListFunc used to kludge Group.Struct usage
// without syntax error.
code: Id("a").Op(":=").ListFunc(func(g *Group) { g.StructFunc(func(g *Group) {}) }).Values(),
expect: `a := struct{}{}`,
},
{
desc: `structfunc func`,
code: Id("a").Op(":=").Add(StructFunc(func(g *Group) {})).Values(),
expect: `a := struct{}{}`,
},
{
desc: `struct group`,
// Don't do this! ListFunc used to kludge Group.Struct usage
// without syntax error.
code: Id("a").Op(":=").ListFunc(func(g *Group) { g.Struct() }).Values(),
expect: `a := struct{}{}`,
},
{
desc: `struct func`,
code: Id("a").Op(":=").Add(Struct()).Values(),
expect: `a := struct{}{}`,
},
{
desc: `interfacefunc func`,
code: Id("a").Assert(InterfaceFunc(func(g *Group) {
g.Id("a").Call().Int()
g.Id("b").Call().Int()
})),
expect: `a.(interface{
a() int
b() int
})`,
},
{
desc: `interfacefunc statement`,
code: Id("a").Assert(Null().InterfaceFunc(func(g *Group) {
g.Id("a").Call().Int()
g.Id("b").Call().Int()
})),
expect: `a.(interface{
a() int
b() int
})`,
},
{
desc: `interfacefunc group`,
// Don't do this! ListFunc used to kludge Group.InterfaceFunc usage
// without syntax error.
code: Id("a").Assert(ListFunc(func(lg *Group) {
lg.InterfaceFunc(func(ig *Group) {
ig.Id("a").Call().Int()
ig.Id("b").Call().Int()
})
})),
expect: `a.(interface{
a() int
b() int
})`,
},
{
desc: `interface func`,
code: Interface().Parens(Id("a")),
expect: `interface{}(a)`,
},
{
desc: `interface group`,
code: BlockFunc(func(g *Group) {
g.Interface().Parens(Id("a"))
}),
expect: `{
interface{}(a)
}`,
},
{
desc: `interface statement`,
code: Null().Interface().Parens(Id("a")),
expect: `interface{}(a)`,
},
{
desc: `switchfunc func`,
code: SwitchFunc(func(rg *Group) {
rg.Id("a")
}).Block(),
expect: `switch a {}`,
},
{
desc: `switchfunc statement`,
code: Null().SwitchFunc(func(rg *Group) {
rg.Id("a")
}).Block(),
expect: `switch a {
}`,
},
{
desc: `switchfunc group`,
code: BlockFunc(func(bg *Group) {
bg.SwitchFunc(func(rg *Group) {
rg.Id("a")
}).Block()
}),
expect: `{
switch a {
}
}`,
},
{
desc: `switch group`,
code: BlockFunc(func(bg *Group) {
bg.Switch().Block()
}),
expect: `{
switch {
}
}`,
},
{
desc: `forfunc func`,
code: ForFunc(func(rg *Group) {
rg.Id("a")
}).Block(),
expect: `for a {}`,
},
{
desc: `forfunc statement`,
code: Null().ForFunc(func(rg *Group) {
rg.Id("a")
}).Block(),
expect: `for a {
}`,
},
{
desc: `forfunc group`,
code: BlockFunc(func(bg *Group) {
bg.ForFunc(func(rg *Group) {
rg.Id("a")
}).Block()
}),
expect: `{
for a {
}
}`,
},
{
desc: `for group`,
code: BlockFunc(func(g *Group) {
g.For(Id("a")).Block()
}),
expect: `{
for a {}
}`,
},
{
desc: `returnfunc func`,
code: ReturnFunc(func(rg *Group) {
rg.Lit(1)
rg.Lit(2)
}),
expect: `return 1, 2`,
},
{
desc: `returnfunc statement`,
code: Empty().ReturnFunc(func(rg *Group) {
rg.Lit(1)
rg.Lit(2)
}),
expect: `return 1, 2`,
},
{
desc: `returnfunc group`,
code: BlockFunc(func(bg *Group) {
bg.ReturnFunc(func(rg *Group) {
rg.Lit(1)
rg.Lit(2)
})
}),
expect: `{
return 1, 2
}`,
},
{
desc: `return group`,
code: BlockFunc(func(g *Group) {
g.Return()
}),
expect: `{
return
}`,
},
{
desc: `iffunc group`,
code: BlockFunc(func(bg *Group) {
bg.IfFunc(func(ig *Group) {
ig.Id("a")
}).Block()
}),
expect: `{
if a {}
}`,
},
{
desc: `iffunc func`,
code: IfFunc(func(ig *Group) {
ig.Id("a")
}).Block(),
expect: `if a {}`,
},
{
desc: `iffunc statement`,
code: Null().IfFunc(func(ig *Group) {
ig.Id("a")
}).Block(),
expect: `if a {}`,
},
{
desc: `if group`,
code: BlockFunc(func(g *Group) { g.If(Id("a")).Block() }),
expect: `{
if a {}
}`,
},
{
desc: `map group`,
code: BlockFunc(func(g *Group) { g.Map(Int()).Int().Values(Dict{Lit(1): Lit(1)}) }),
expect: `{
map[int]int{1:1}
}`,
},
{
desc: `assert group`,
// Don't do this! ListFunc used to kludge Group.Assert usage without
// syntax error.
code: Id("a").ListFunc(func(g *Group) { g.Assert(Id("b")) }),
expect: `a.(b)`,
},
{
desc: `assert func`,
code: Id("a").Add(Assert(Id("b"))),
expect: `a.(b)`,
},
{
desc: `paramsfunc group`,
// Don't do this! ListFunc used to kludge Group.ParamsFunc usage without
// syntax error.
code: Id("a").ListFunc(func(lg *Group) { lg.ParamsFunc(func(cg *Group) { cg.Lit(1) }) }),
expect: `a(1)`,
},
{
desc: `paramsfunc func`,
code: Id("a").Add(ParamsFunc(func(g *Group) { g.Lit(1) })),
expect: `a(1)`,
},
{
desc: `paramsfunc statement`,
code: Id("a").ParamsFunc(func(g *Group) { g.Lit(1) }),
expect: `a(1)`,
},
{
desc: `params group`,
// Don't do this! ListFunc used to kludge Group.Params usage without
// syntax error.
code: Id("a").ListFunc(func(g *Group) { g.Params(Lit(1)) }),
expect: `a(1)`,
},
{
desc: `params func`,
code: Id("a").Add(Params(Lit(1))),
expect: `a(1)`,
},
{
desc: `callfunc group`,
// Don't do this! ListFunc used to kludge Group.CallFunc usage without
// syntax error.
code: Id("a").ListFunc(func(lg *Group) { lg.CallFunc(func(cg *Group) { cg.Lit(1) }) }),
expect: `a(1)`,
},
{
desc: `callfunc func`,
code: Id("a").Add(CallFunc(func(g *Group) { g.Lit(1) })),
expect: `a(1)`,
},
{
desc: `call group`,
// Don't do this! ListFunc used to kludge Group.Call usage without
// syntax error.
code: Id("a").ListFunc(func(g *Group) { g.Call(Lit(1)) }),
expect: `a(1)`,
},
{
desc: `call func`,
code: Id("a").Add(Call(Lit(1))),
expect: `a(1)`,
},
{
desc: `defsfunc statement`,
code: Const().DefsFunc(func(g *Group) { g.Id("a").Op("=").Lit(1) }),
expect: `const (
a = 1
)`,
},
{
desc: `defsfunc func`,
code: Const().Add(DefsFunc(func(g *Group) { g.Id("a").Op("=").Lit(1) })),
expect: `const (
a = 1
)`,
},
{
desc: `defsfunc group`,
// Don't do this! ListFunc used to kludge Group.DefsFunc usage without
// syntax error.
code: Const().ListFunc(func(lg *Group) { lg.DefsFunc(func(dg *Group) { dg.Id("a").Op("=").Lit(1) }) }),
expect: `const (
a = 1
)`,
},
{
desc: `defs group`,
// Don't do this! ListFunc used to kludge Group.Defs usage without
// syntax error.
code: Const().ListFunc(func(g *Group) { g.Defs(Id("a").Op("=").Lit(1)) }),
expect: `const (
a = 1
)`,
},
{
desc: `defs func`,
code: Const().Add(Defs(Id("a").Op("=").Lit(1))),
expect: `const (
a = 1
)`,
},
{
desc: `blockfunc group`,
code: BlockFunc(func(g *Group) { g.BlockFunc(func(g *Group) {}) }),
expect: `{{}}`,
},
{
desc: `block group`,
code: BlockFunc(func(g *Group) { g.Block() }),
expect: `{{}}`,
},
{
desc: `indexfunc group`,
code: BlockFunc(func(g *Group) { g.IndexFunc(func(g *Group) { g.Lit(1) }).Int().Values(Lit(1)) }),
expect: `{[1]int{1}}`,
},
{
desc: `indexfunc statement`,
code: Id("a").IndexFunc(func(g *Group) { g.Lit(1) }),
expect: `a[1]`,
},
{
desc: `indexfunc func`,
code: Id("a").Add(IndexFunc(func(g *Group) { g.Lit(1) })),
expect: `a[1]`,
},
{
desc: `index group`,
code: BlockFunc(func(g *Group) { g.Index(Lit(1)).Int().Values(Lit(1)) }),
expect: `{[1]int{1}}`,
},
{
desc: `index func`,
code: Id("a").Add(Index(Lit(1))),
expect: `a[1]`,
},
{
desc: `valuesfunc func`,
code: ValuesFunc(func(vg *Group) {
vg.Lit(1)
}),
expect: `{1}`,
},
{
desc: `valuesfunc group`,
code: BlockFunc(func(bg *Group) {
bg.ValuesFunc(func(vg *Group) {
vg.Lit(1)
})
}),
expect: `{
{1}
}`,
},
{
desc: `values group`,
code: BlockFunc(func(g *Group) {
g.Values(Lit(1))
}),
expect: `{
{1}
}`,
},
{
desc: `listfunc statement`,
code: Add(Null()).ListFunc(func(lg *Group) {
lg.Id("a")
lg.Id("b")
}).Op("=").Id("c"),
expect: `a, b = c`,
},
{
desc: `listfunc func`,
code: ListFunc(func(lg *Group) {
lg.Id("a")
lg.Id("b")
}).Op("=").Id("c"),
expect: `a, b = c`,
},
{
desc: `listfunc group`,
code: BlockFunc(func(bg *Group) {
bg.ListFunc(func(lg *Group) {
lg.Id("a")
lg.Id("b")
}).Op("=").Id("c")
}),
expect: `{
a, b = c
}`,
},
{
desc: `list group`,
code: BlockFunc(func(g *Group) { g.List(Id("a"), Id("b")).Op("=").Id("c") }),
expect: `{
a, b = c
}`,
},
{
desc: `parens func`,
code: Parens(Lit(1)),
expect: `(1)`,
},
{
desc: `parens group`,
code: BlockFunc(func(g *Group) { g.Parens(Lit(1)) }),
expect: `{
(1)
}`,
},
}
func TestGen(t *testing.T) {
caseTester(t, gencases)
}
+147
View File
@@ -0,0 +1,147 @@
package jen
import (
"bytes"
"fmt"
"go/format"
"io"
)
// Group represents a list of Code items, separated by tokens with an optional
// open and close token.
type Group struct {
name string
items []Code
open string
close string
separator string
multi bool
}
func (g *Group) isNull(f *File) bool {
if g == nil {
return true
}
if g.open != "" || g.close != "" {
return false
}
for _, c := range g.items {
if !c.isNull(f) {
return false
}
}
return true
}
func (g *Group) render(f *File, w io.Writer, s *Statement) error {
if g.name == "block" && s != nil {
// Special CaseBlock format for then the previous item in the statement
// is a Case group or the default keyword.
prev := s.previous(g)
grp, isGrp := prev.(*Group)
tkn, isTkn := prev.(token)
if isGrp && grp.name == "case" || isTkn && tkn.content == "default" {
g.open = ""
g.close = ""
}
}
if g.open != "" {
if _, err := w.Write([]byte(g.open)); err != nil {
return err
}
}
isNull, err := g.renderItems(f, w)
if err != nil {
return err
}
if !isNull && g.multi && g.close != "" {
// For multi-line blocks with a closing token, we insert a new line after the last item (but
// not if all items were null). This is to ensure that if the statement finishes with a comment,
// the closing token is not commented out.
s := "\n"
if g.separator == "," {
// We also insert add trailing comma if the separator was ",".
s = ",\n"
}
if _, err := w.Write([]byte(s)); err != nil {
return err
}
}
if g.close != "" {
if _, err := w.Write([]byte(g.close)); err != nil {
return err
}
}
return nil
}
func (g *Group) renderItems(f *File, w io.Writer) (isNull bool, err error) {
first := true
for _, code := range g.items {
if pt, ok := code.(token); ok && pt.typ == packageToken {
// Special case for package tokens in Qual groups - for dot-imports, the package token
// will be null, so will not render and will not be registered in the imports block.
// This ensures all packageTokens that are rendered are registered.
f.register(pt.content.(string))
}
if code == nil || code.isNull(f) {
// Null() token produces no output but also
// no separator. Empty() token products no
// output but adds a separator.
continue
}
if g.name == "values" {
if _, ok := code.(Dict); ok && len(g.items) > 1 {
panic("Error in Values: if Dict is used, must be one item only")
}
}
if !first && g.separator != "" {
// The separator token is added before each non-null item, but not before the first item.
if _, err := w.Write([]byte(g.separator)); err != nil {
return false, err
}
}
if g.multi {
// For multi-line blocks, we insert a new line before each non-null item.
if _, err := w.Write([]byte("\n")); err != nil {
return false, err
}
}
if err := code.render(f, w, nil); err != nil {
return false, err
}
first = false
}
return first, nil
}
// Render renders the Group to the provided writer.
func (g *Group) Render(writer io.Writer) error {
return g.RenderWithFile(writer, NewFile(""))
}
// GoString renders the Group for testing. Any error will cause a panic.
func (g *Group) GoString() string {
buf := bytes.Buffer{}
if err := g.Render(&buf); err != nil {
panic(err)
}
return buf.String()
}
// RenderWithFile renders the Group to the provided writer, using imports from the provided file.
func (g *Group) RenderWithFile(writer io.Writer, file *File) error {
buf := &bytes.Buffer{}
if err := g.render(file, buf, nil); err != nil {
return err
}
b, err := format.Source(buf.Bytes())
if err != nil {
return fmt.Errorf("Error %s while formatting source:\n%s", err, buf.String())
}
if _, err := writer.Write(b); err != nil {
return err
}
return nil
}
+35
View File
@@ -0,0 +1,35 @@
package jen_test
import (
"bytes"
"testing"
. "github.com/dave/jennifer/jen"
)
func TestGroup_Render(t *testing.T) {
file := NewFile("main")
file.ImportAlias("fmt", "fmtalias")
var g *Group
BlockFunc(func(group *Group) {
g = group
})
g.Qual("fmt", "Errorf").Call(Lit("error"))
expect := `{
fmtalias.Errorf("error")
}`
var got bytes.Buffer
err := g.RenderWithFile(&got, file)
if err != nil {
t.Fatal(err)
}
if got.String() != expect {
t.Fatalf("Got: %v, expect: %v", got.String(), expect)
}
}
+273
View File
@@ -0,0 +1,273 @@
// This file is generated - do not edit.
package jen
// standardLibraryHints contains package name hints
var standardLibraryHints = map[string]string{
"archive/tar": "tar",
"archive/zip": "zip",
"bufio": "bufio",
"builtin": "builtin",
"bytes": "bytes",
"cmd/asm/internal/arch": "arch",
"cmd/asm/internal/asm": "asm",
"cmd/asm/internal/flags": "flags",
"cmd/asm/internal/lex": "lex",
"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/wasm": "wasm",
"cmd/compile/internal/x86": "x86",
"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/dirhash": "dirhash",
"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/imports": "imports",
"cmd/go/internal/list": "list",
"cmd/go/internal/load": "load",
"cmd/go/internal/modcmd": "modcmd",
"cmd/go/internal/modconv": "modconv",
"cmd/go/internal/modfetch": "modfetch",
"cmd/go/internal/modfetch/codehost": "codehost",
"cmd/go/internal/modfile": "modfile",
"cmd/go/internal/modget": "modget",
"cmd/go/internal/modinfo": "modinfo",
"cmd/go/internal/modload": "modload",
"cmd/go/internal/module": "module",
"cmd/go/internal/mvs": "mvs",
"cmd/go/internal/par": "par",
"cmd/go/internal/run": "run",
"cmd/go/internal/search": "search",
"cmd/go/internal/semver": "semver",
"cmd/go/internal/str": "str",
"cmd/go/internal/test": "test",
"cmd/go/internal/tool": "tool",
"cmd/go/internal/txtar": "txtar",
"cmd/go/internal/version": "version",
"cmd/go/internal/vet": "vet",
"cmd/go/internal/web": "web",
"cmd/go/internal/web2": "web2",
"cmd/go/internal/webtest": "webtest",
"cmd/go/internal/work": "work",
"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/wasm": "wasm",
"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/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/wasm": "wasm",
"cmd/link/internal/x86": "x86",
"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/randutil": "randutil",
"crypto/internal/subtle": "subtle",
"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/bytealg": "bytealg",
"internal/cpu": "cpu",
"internal/nettrace": "nettrace",
"internal/poll": "poll",
"internal/race": "race",
"internal/singleflight": "singleflight",
"internal/syscall/unix": "unix",
"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",
}
+168
View File
@@ -0,0 +1,168 @@
// Package jen is a code generator for Go
package jen
import (
"bytes"
"fmt"
"go/format"
"io"
"io/ioutil"
"sort"
"strconv"
)
// Code represents an item of code that can be rendered.
type Code interface {
render(f *File, w io.Writer, s *Statement) error
isNull(f *File) bool
}
// Save renders the file and saves to the filename provided.
func (f *File) Save(filename string) error {
// notest
buf := &bytes.Buffer{}
if err := f.Render(buf); err != nil {
return err
}
if err := ioutil.WriteFile(filename, buf.Bytes(), 0644); err != nil {
return err
}
return nil
}
// Render renders the file to the provided writer.
func (f *File) Render(w io.Writer) error {
body := &bytes.Buffer{}
if err := f.render(f, body, nil); err != nil {
return err
}
source := &bytes.Buffer{}
if len(f.headers) > 0 {
for _, c := range f.headers {
if err := Comment(c).render(f, source, nil); err != nil {
return err
}
if _, err := fmt.Fprint(source, "\n"); err != nil {
return err
}
}
// Append an extra newline so that header comments don't get lumped in
// with package comments.
if _, err := fmt.Fprint(source, "\n"); err != nil {
return err
}
}
for _, c := range f.comments {
if err := Comment(c).render(f, source, nil); err != nil {
return err
}
if _, err := fmt.Fprint(source, "\n"); err != nil {
return err
}
}
if _, err := fmt.Fprintf(source, "package %s", f.name); err != nil {
return err
}
if f.CanonicalPath != "" {
if _, err := fmt.Fprintf(source, " // import %q", f.CanonicalPath); err != nil {
return err
}
}
if _, err := fmt.Fprint(source, "\n\n"); err != nil {
return err
}
if err := f.renderImports(source); err != nil {
return err
}
if _, err := source.Write(body.Bytes()); err != nil {
return err
}
formatted, err := format.Source(source.Bytes())
if err != nil {
return fmt.Errorf("Error %s while formatting source:\n%s", err, source.String())
}
if _, err := w.Write(formatted); err != nil {
return err
}
return nil
}
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"].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]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] = def
}
if len(filtered) == 1 {
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\n\n", strconv.Quote(path)); err != nil {
return err
}
}
}
} else if len(filtered) > 1 {
if _, err := fmt.Fprint(source, "import (\n"); err != nil {
return err
}
// We must sort the imports to ensure repeatable
// source.
paths := []string{}
for path := range filtered {
paths = append(paths, path)
}
sort.Strings(paths)
for _, path := range paths {
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\n", strconv.Quote(path)); err != nil {
return err
}
}
}
if _, err := fmt.Fprint(source, ")\n\n"); err != nil {
return err
}
}
if separateCgo {
for _, c := range f.cgoPreamble {
if err := Comment(c).render(f, source, nil); err != nil {
return err
}
if _, err := fmt.Fprint(source, "\n"); err != nil {
return err
}
}
if _, err := fmt.Fprint(source, "import \"C\"\n\n"); err != nil {
return err
}
}
return nil
}
+550
View File
@@ -0,0 +1,550 @@
package jen_test
import (
"fmt"
"go/format"
"strings"
"testing"
. "github.com/dave/jennifer/jen"
)
var o1 = Options{
Close: ")",
Multi: true,
Open: "(",
Separator: ",",
}
var o2 = Options{
Close: "",
Multi: false,
Open: "",
Separator: ",",
}
var cases = []tc{
{
desc: `scientific notation`,
code: Lit(1e3),
expect: `1000.0`,
},
{
desc: `big float`,
code: Lit(1000000.0),
expect: `1e+06`,
},
{
desc: `lit float whole numbers`,
code: Index().Float64().Values(Lit(-10.0), Lit(-2.0), Lit(-1.0), Lit(0.0), Lit(1.0), Lit(2.0), Lit(10.0)),
expect: "[]float64{-10.0, -2.0, -1.0, 0.0, 1.0, 2.0, 10.0}",
},
{
desc: `custom func group`,
code: ListFunc(func(g *Group) {
g.CustomFunc(o2, func(g *Group) {
g.Id("a")
g.Id("b")
g.Id("c")
})
}).Op("=").Id("foo").Call(),
expect: `a, b, c = foo()`,
},
{
desc: `custom group`,
code: ListFunc(func(g *Group) { g.Custom(o2, Id("a"), Id("b"), Id("c")) }).Op("=").Id("foo").Call(),
expect: `a, b, c = foo()`,
},
{
desc: `custom function`,
code: Id("foo").Add(Custom(o1, Lit("a"), Lit("b"), Lit("c"))),
expect: `foo(
"a",
"b",
"c",
)`,
},
{
desc: `custom function`,
code: Id("foo").Add(Custom(o1, Lit("a"), Lit("b"), Lit("c"))),
expect: `foo(
"a",
"b",
"c",
)`,
},
{
desc: `line statement`,
code: Block(Lit(1).Line(), Lit(2)),
expect: `{
1
2
}`,
},
{
desc: `line func`,
code: Block(Lit(1), Line(), Lit(2)),
expect: `{
1
2
}`,
},
{
desc: `line group`,
code: BlockFunc(func(g *Group) {
g.Id("a")
g.Line()
g.Id("b")
}),
expect: `{
a
b
}`,
},
{
desc: `op group`,
code: BlockFunc(func(g *Group) {
g.Op("*").Id("a")
}),
expect: `{*a}`,
},
{
desc: `empty group`,
code: BlockFunc(func(g *Group) {
g.Empty()
}),
expect: `{
}`,
},
{
desc: `null group`,
code: BlockFunc(func(g *Group) {
g.Null()
}),
expect: `{}`,
},
{
desc: `tag no backquote`,
code: Tag(map[string]string{"a": "`b`"}),
expect: "\"a:\\\"`b`\\\"\"",
},
{
desc: `tag null`,
code: Tag(map[string]string{}),
expect: ``,
},
{
desc: `litrunefunc group`,
code: BlockFunc(func(g *Group) {
g.LitByteFunc(func() byte { return byte(0xab) })
}),
expect: `{byte(0xab)}`,
},
{
desc: `litbyte group`,
code: BlockFunc(func(g *Group) {
g.LitByte(byte(0xab))
}),
expect: `{byte(0xab)}`,
},
{
desc: `litrunefunc group`,
code: BlockFunc(func(g *Group) {
g.LitRuneFunc(func() rune { return 'a' })
}),
expect: `{'a'}`,
},
{
desc: `litrune group`,
code: BlockFunc(func(g *Group) {
g.LitRune('a')
}),
expect: `{'a'}`,
},
{
desc: `litfunc group`,
code: BlockFunc(func(g *Group) {
g.LitFunc(func() interface{} {
return 1 + 1
})
}),
expect: `{2}`,
},
{
desc: `litfunc func`,
code: LitFunc(func() interface{} {
return 1 + 1
}),
expect: `2`,
},
{
desc: `group all null`,
code: List(Null(), Null()),
expect: ``,
},
{
desc: `do group`,
code: BlockFunc(func(g *Group) { g.Do(func(s *Statement) { s.Lit(1) }) }),
expect: `{1}`,
},
{
desc: `do func`,
code: Do(func(s *Statement) { s.Lit(1) }),
expect: `1`,
},
{
desc: `dict empty`,
code: Values(Dict{}),
expect: `{}`,
},
{
desc: `dict null`,
code: Values(Dict{Null(): Null()}),
expect: `{}`,
},
{
desc: `commentf group`,
code: BlockFunc(func(g *Group) { g.Commentf("%d", 1) }),
expect: `{
// 1
}`,
},
{
desc: `commentf func`,
code: Commentf("%d", 1),
expect: `// 1`,
},
{
desc: `add func`,
code: Add(Lit(1)),
expect: `1`,
},
{
desc: `add group`,
code: BlockFunc(func(g *Group) { g.Add(Lit(1)) }),
expect: `{1}`,
},
{
desc: `empty block`,
code: Block(),
expect: `{}`,
},
{
desc: `string literal`,
code: Lit("a"),
expect: `"a"`,
},
{
desc: `int literal`,
code: Lit(1),
expect: `1`,
},
{
desc: `simple id`,
code: Id("a"),
expect: `a`,
},
{
desc: `foreign id`,
code: Qual("x.y/z", "a"),
expect: `z.a`,
expectImports: map[string]string{
"x.y/z": "z",
},
},
{
desc: `var decl`,
code: Var().Id("a").Op("=").Lit("b"),
expect: `var a = "b"`,
},
{
desc: `short var decl`,
code: Id("a").Op(":=").Lit("b"),
expect: `a := "b"`,
},
{
desc: `simple if`,
code: If(Id("a").Op("==").Lit("b")).Block(),
expect: `if a == "b" {}`,
},
{
desc: `simple if`,
code: If(Id("a").Op("==").Lit("b")).Block(
Id("a").Op("++"),
),
expect: `if a == "b" { a++ }`,
},
{
desc: `pointer`,
code: Op("*").Id("a"),
expect: `*a`,
},
{
desc: `address`,
code: Op("&").Id("a"),
expect: `&a`,
},
{
desc: `simple call`,
code: Id("a").Call(
Lit("b"),
Lit("c"),
),
expect: `a("b", "c")`,
},
{
desc: `call fmt.Sprintf`,
code: Qual("fmt", "Sprintf").Call(
Lit("b"),
Id("c"),
),
expect: `fmt.Sprintf("b", c)`,
},
{
desc: `slices`,
code: Id("a").Index(
Lit(1),
Empty(),
),
expect: `a[1:]`,
},
{
desc: `return`,
code: Return(Id("a")),
expect: `return a`,
},
{
desc: `double return`,
code: Return(Id("a"), Id("b")),
expect: `return a, b`,
},
{
desc: `func`,
code: Func().Id("a").Params(
Id("a").String(),
).Block(
Return(Id("a")),
),
expect: `func a(a string){
return a
}`,
},
{
desc: `built in func`,
code: New(Id("a")),
expect: `new(a)`,
},
{
desc: `multip`,
code: Id("a").Op("*").Id("b"),
expect: `a * b`,
},
{
desc: `multip ptr`,
code: Id("a").Op("*").Op("*").Id("b"),
expect: `a * *b`,
},
{
desc: `field`,
code: Id("a").Dot("b"),
expect: `a.b`,
},
{
desc: `method`,
code: Id("a").Dot("b").Call(Id("c"), Id("d")),
expect: `a.b(c, d)`,
},
{
desc: `if else`,
code: If(Id("a").Op("==").Lit(1)).Block(
Id("b").Op("=").Lit(1),
).Else().If(Id("a").Op("==").Lit(2)).Block(
Id("b").Op("=").Lit(2),
).Else().Block(
Id("b").Op("=").Lit(3),
),
expect: `if a == 1 { b = 1 } else if a == 2 { b = 2 } else { b = 3 }`,
},
{
desc: `literal array`,
code: Index().String().Values(Lit("a"), Lit("b")),
expect: `[]string{"a", "b"}`,
},
{
desc: `comment`,
code: Comment("a"),
expect: `// a`,
},
{
desc: `null`,
code: Id("a").Params(Id("b"), Null(), Id("c")),
expect: `a(b, c)`,
},
{
desc: `map literal single`,
code: Id("a").Values(Dict{
Id("b"): Id("c"),
}),
expect: `a{b: c}`,
},
{
desc: `map literal null`,
code: Id("a").Values(Dict{
Null(): Id("c"),
Id("b"): Null(),
Id("b"): Id("c"),
}),
expect: `a{b: c}`,
},
{
desc: `map literal multiple`,
code: Id("a").Values(Dict{
Id("b"): Id("c"),
Id("d"): Id("e"),
}),
expect: `a{
b: c,
d: e,
}`,
},
{
desc: `map literal func single`,
code: Id("a").Values(DictFunc(func(d Dict) {
d[Id("b")] = Id("c")
})),
expect: `a{b: c}`,
},
{
desc: `map literal func single null`,
code: Id("a").Values(DictFunc(func(d Dict) {
d[Null()] = Id("c")
d[Id("b")] = Null()
d[Id("b")] = Id("c")
})),
expect: `a{b: c}`,
},
{
desc: `map literal func multiple`,
code: Id("a").Values(DictFunc(func(d Dict) {
d[Id("b")] = Id("c")
d[Id("d")] = Id("e")
})),
expect: `a{
b: c,
d: e,
}`,
},
{
desc: `literal func`,
code: Id("a").Op(":=").LitFunc(func() interface{} {
return "b"
}),
expect: `a := "b"`,
},
{
desc: `dot`,
code: Id("a").Dot("b").Dot("c"),
expect: `a.b.c`,
},
{
desc: `do`,
code: Id("a").Do(func(s *Statement) { s.Dot("b") }),
expect: `a.b`,
},
{
desc: `tags should be ordered`,
code: Tag(map[string]string{"z": "1", "a": "2"}),
expect: "`a:\"2\" z:\"1\"`",
},
{
desc: `dict should be ordered`,
code: Map(String()).Int().Values(Dict{Id("z"): Lit(1), Id("a"): Lit(2)}),
expect: `map[string]int{
a:2,
z:1,
}`,
},
}
func TestJen(t *testing.T) {
caseTester(t, cases)
}
func caseTester(t *testing.T, cases []tc) {
for i, c := range cases {
onlyTest := ""
if onlyTest != "" && c.desc != onlyTest {
continue
}
rendered := fmt.Sprintf("%#v", c.code)
expected, err := format.Source([]byte(c.expect))
if err != nil {
panic(fmt.Sprintf("Error formatting expected source in test case %d. Description: %s\nError:\n%s", i, c.desc, err))
}
if strings.TrimSpace(string(rendered)) != strings.TrimSpace(string(expected)) {
t.Errorf("Test case %d failed. Description: %s\nExpected:\n%s\nOutput:\n%s", i, c.desc, expected, rendered)
}
//if c.expectImports != nil {
// f := FromContext(ctx)
// if !reflect.DeepEqual(f.Imports, c.expectImports) {
// t.Errorf("Test case %d failed. Description: %s\nImports expected:\n%s\nOutput:\n%s", i, c.desc, c.expectImports, f.Imports)
// }
//}
}
}
// a test case
type tc struct {
// path
path string
// description for locating the test case
desc string
// code to generate
code Code
// expected generated source
expect string
// expected imports
expectImports map[string]string
}
func TestNilStatement(t *testing.T) {
var s *Statement
c := Func().Id("a").Params(
s,
)
got := fmt.Sprintf("%#v", c)
expect := "func a()"
if got != expect {
t.Fatalf("Got: %s, expect: %s", got, expect)
}
}
func TestNilGroup(t *testing.T) {
var g *Group
c := Func().Id("a").Params(
g,
)
got := fmt.Sprintf("%#v", c)
expect := "func a()"
if got != expect {
t.Fatalf("Got: %s, expect: %s", got, expect)
}
}
func TestGroup_GoString(t *testing.T) {
BlockFunc(func(g *Group) {
g.Lit(1)
got := fmt.Sprintf("%#v", g)
expect := "{\n\t1\n}"
if got != expect {
t.Fatalf("Got: %s, expect: %s", got, expect)
}
})
}
+154
View File
@@ -0,0 +1,154 @@
package jen
// Lit renders a literal. Lit supports only built-in types (bool, string, int, complex128, float64,
// float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr and complex64).
// Passing any other type will panic.
func Lit(v interface{}) *Statement {
return newStatement().Lit(v)
}
// Lit renders a literal. Lit supports only built-in types (bool, string, int, complex128, float64,
// float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr and complex64).
// Passing any other type will panic.
func (g *Group) Lit(v interface{}) *Statement {
s := Lit(v)
g.items = append(g.items, s)
return s
}
// Lit renders a literal. Lit supports only built-in types (bool, string, int, complex128, float64,
// float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr and complex64).
// Passing any other type will panic.
func (s *Statement) Lit(v interface{}) *Statement {
t := token{
typ: literalToken,
content: v,
}
*s = append(*s, t)
return s
}
// LitFunc renders a literal. LitFunc generates the value to render by executing the provided
// function. LitFunc supports only built-in types (bool, string, int, complex128, float64, float32,
// int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr and complex64).
// Returning any other type will panic.
func LitFunc(f func() interface{}) *Statement {
return newStatement().LitFunc(f)
}
// LitFunc renders a literal. LitFunc generates the value to render by executing the provided
// function. LitFunc supports only built-in types (bool, string, int, complex128, float64, float32,
// int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr and complex64).
// Returning any other type will panic.
func (g *Group) LitFunc(f func() interface{}) *Statement {
s := LitFunc(f)
g.items = append(g.items, s)
return s
}
// LitFunc renders a literal. LitFunc generates the value to render by executing the provided
// function. LitFunc supports only built-in types (bool, string, int, complex128, float64, float32,
// int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr and complex64).
// Returning any other type will panic.
func (s *Statement) LitFunc(f func() interface{}) *Statement {
t := token{
typ: literalToken,
content: f(),
}
*s = append(*s, t)
return s
}
// LitRune renders a rune literal.
func LitRune(v rune) *Statement {
return newStatement().LitRune(v)
}
// LitRune renders a rune literal.
func (g *Group) LitRune(v rune) *Statement {
s := LitRune(v)
g.items = append(g.items, s)
return s
}
// LitRune renders a rune literal.
func (s *Statement) LitRune(v rune) *Statement {
t := token{
typ: literalRuneToken,
content: v,
}
*s = append(*s, t)
return s
}
// LitRuneFunc renders a rune literal. LitRuneFunc generates the value to
// render by executing the provided function.
func LitRuneFunc(f func() rune) *Statement {
return newStatement().LitRuneFunc(f)
}
// LitRuneFunc renders a rune literal. LitRuneFunc generates the value to
// render by executing the provided function.
func (g *Group) LitRuneFunc(f func() rune) *Statement {
s := LitRuneFunc(f)
g.items = append(g.items, s)
return s
}
// LitRuneFunc renders a rune literal. LitRuneFunc generates the value to
// render by executing the provided function.
func (s *Statement) LitRuneFunc(f func() rune) *Statement {
t := token{
typ: literalRuneToken,
content: f(),
}
*s = append(*s, t)
return s
}
// LitByte renders a byte literal.
func LitByte(v byte) *Statement {
return newStatement().LitByte(v)
}
// LitByte renders a byte literal.
func (g *Group) LitByte(v byte) *Statement {
s := LitByte(v)
g.items = append(g.items, s)
return s
}
// LitByte renders a byte literal.
func (s *Statement) LitByte(v byte) *Statement {
t := token{
typ: literalByteToken,
content: v,
}
*s = append(*s, t)
return s
}
// LitByteFunc renders a byte literal. LitByteFunc generates the value to
// render by executing the provided function.
func LitByteFunc(f func() byte) *Statement {
return newStatement().LitByteFunc(f)
}
// LitByteFunc renders a byte literal. LitByteFunc generates the value to
// render by executing the provided function.
func (g *Group) LitByteFunc(f func() byte) *Statement {
s := LitByteFunc(f)
g.items = append(g.items, s)
return s
}
// LitByteFunc renders a byte literal. LitByteFunc generates the value to
// render by executing the provided function.
func (s *Statement) LitByteFunc(f func() byte) *Statement {
t := token{
typ: literalByteToken,
content: f(),
}
*s = append(*s, t)
return s
}
+101
View File
@@ -0,0 +1,101 @@
package jen
import (
"bytes"
"fmt"
"go/format"
"io"
)
// Statement represents a simple list of code items. When rendered the items
// are separated by spaces.
type Statement []Code
func newStatement() *Statement {
return &Statement{}
}
// Clone makes a copy of the Statement, so further tokens can be appended
// without affecting the original.
func (s *Statement) Clone() *Statement {
return &Statement{s}
}
func (s *Statement) previous(c Code) Code {
index := -1
for i, item := range *s {
if item == c {
index = i
break
}
}
if index > 0 {
return (*s)[index-1]
}
return nil
}
func (s *Statement) isNull(f *File) bool {
if s == nil {
return true
}
for _, c := range *s {
if !c.isNull(f) {
return false
}
}
return true
}
func (s *Statement) render(f *File, w io.Writer, _ *Statement) error {
first := true
for _, code := range *s {
if code == nil || code.isNull(f) {
// Null() token produces no output but also
// no separator. Empty() token products no
// output but adds a separator.
continue
}
if !first {
if _, err := w.Write([]byte(" ")); err != nil {
return err
}
}
if err := code.render(f, w, s); err != nil {
return err
}
first = false
}
return nil
}
// Render renders the Statement to the provided writer.
func (s *Statement) Render(writer io.Writer) error {
return s.RenderWithFile(writer, NewFile(""))
}
// GoString renders the Statement for testing. Any error will cause a panic.
func (s *Statement) GoString() string {
buf := bytes.Buffer{}
if err := s.Render(&buf); err != nil {
panic(err)
}
return buf.String()
}
// RenderWithFile renders the Statement to the provided writer, using imports from the provided file.
func (s *Statement) RenderWithFile(writer io.Writer, file *File) error {
buf := &bytes.Buffer{}
if err := s.render(file, buf, nil); err != nil {
return err
}
b, err := format.Source(buf.Bytes())
if err != nil {
return fmt.Errorf("Error %s while formatting source:\n%s", err, buf.String())
}
if _, err := writer.Write(b); err != nil {
return err
}
return nil
}
+32
View File
@@ -0,0 +1,32 @@
package jen_test
import (
"bytes"
"testing"
. "github.com/dave/jennifer/jen"
)
func TestStatement_Render(t *testing.T) {
file := NewFile("main")
file.ImportAlias("fmt", "fmtalias")
statement := file.Func().Id("main").Params().Block(
Qual("fmt", "Println").Call(Lit("something")),
)
expect := `func main() {
fmtalias.Println("something")
}`
var got bytes.Buffer
err := statement.RenderWithFile(&got, file)
if err != nil {
t.Fatal(err)
}
if got.String() != expect {
t.Fatalf("Got: %v, expect: %v", got.String(), expect)
}
}
+76
View File
@@ -0,0 +1,76 @@
package jen
import (
"fmt"
"io"
"sort"
"strconv"
)
// Tag renders a struct tag
func Tag(items map[string]string) *Statement {
return newStatement().Tag(items)
}
// Tag renders a struct tag
func (g *Group) Tag(items map[string]string) *Statement {
// notest
// don't think this can ever be used in valid code?
s := Tag(items)
g.items = append(g.items, s)
return s
}
// Tag renders a struct tag
func (s *Statement) Tag(items map[string]string) *Statement {
c := tag{
items: items,
}
*s = append(*s, c)
return s
}
type tag struct {
items map[string]string
}
func (t tag) isNull(f *File) bool {
return len(t.items) == 0
}
func (t tag) render(f *File, w io.Writer, s *Statement) error {
if t.isNull(f) {
// notest
// render won't be called if t is null
return nil
}
var str string
var sorted []string
for k := range t.items {
sorted = append(sorted, k)
}
sort.Strings(sorted)
for _, k := range sorted {
v := t.items[k]
if len(str) > 0 {
str += " "
}
str += fmt.Sprintf(`%s:"%s"`, k, v)
}
if strconv.CanBackquote(str) {
str = "`" + str + "`"
} else {
str = strconv.Quote(str)
}
if _, err := w.Write([]byte(str)); err != nil {
return err
}
return nil
}
+301
View File
@@ -0,0 +1,301 @@
package jen
import (
"fmt"
"io"
"strconv"
"strings"
)
type tokenType string
const (
packageToken tokenType = "package"
identifierToken tokenType = "identifier"
qualifiedToken tokenType = "qualified"
keywordToken tokenType = "keyword"
operatorToken tokenType = "operator"
delimiterToken tokenType = "delimiter"
literalToken tokenType = "literal"
literalRuneToken tokenType = "literal_rune"
literalByteToken tokenType = "literal_byte"
nullToken tokenType = "null"
layoutToken tokenType = "layout"
)
type token struct {
typ tokenType
content interface{}
}
func (t token) isNull(f *File) bool {
if t.typ == packageToken {
// package token is null if the path is a dot-import or the local package path
return f.isDotImport(t.content.(string)) || f.isLocal(t.content.(string))
}
return t.typ == nullToken
}
func (t token) render(f *File, w io.Writer, s *Statement) error {
switch t.typ {
case literalToken:
var out string
switch t.content.(type) {
case bool, string, int, complex128:
// default constant types can be left bare
out = fmt.Sprintf("%#v", t.content)
case float64:
out = fmt.Sprintf("%#v", t.content)
if !strings.Contains(out, ".") && !strings.Contains(out, "e") {
// If the formatted value is not in scientific notation, and does not have a dot, then
// we add ".0". Otherwise it will be interpreted as an int.
// See:
// https://github.com/dave/jennifer/issues/39
// https://github.com/golang/go/issues/26363
out += ".0"
}
case float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr:
// other built-in types need specific type info
out = fmt.Sprintf("%T(%#v)", t.content, t.content)
case complex64:
// fmt package already renders parenthesis for complex64
out = fmt.Sprintf("%T%#v", t.content, t.content)
default:
panic(fmt.Sprintf("unsupported type for literal: %T", t.content))
}
if _, err := w.Write([]byte(out)); err != nil {
return err
}
case literalRuneToken:
if _, err := w.Write([]byte(strconv.QuoteRune(t.content.(rune)))); err != nil {
return err
}
case literalByteToken:
if _, err := w.Write([]byte(fmt.Sprintf("byte(%#v)", t.content))); err != nil {
return err
}
case keywordToken, operatorToken, layoutToken, delimiterToken:
if _, err := w.Write([]byte(fmt.Sprintf("%s", t.content))); err != nil {
return err
}
if t.content.(string) == "default" {
// Special case for Default, which must always be followed by a colon
if _, err := w.Write([]byte(":")); err != nil {
return err
}
}
case packageToken:
path := t.content.(string)
alias := f.register(path)
if _, err := w.Write([]byte(alias)); err != nil {
return err
}
case identifierToken:
if _, err := w.Write([]byte(t.content.(string))); err != nil {
return err
}
case nullToken: // notest
// do nothing (should never render a null token)
}
return nil
}
// Null adds a null item. Null items render nothing and are not followed by a
// separator in lists.
func Null() *Statement {
return newStatement().Null()
}
// Null adds a null item. Null items render nothing and are not followed by a
// separator in lists.
func (g *Group) Null() *Statement {
s := Null()
g.items = append(g.items, s)
return s
}
// Null adds a null item. Null items render nothing and are not followed by a
// separator in lists.
func (s *Statement) Null() *Statement {
t := token{
typ: nullToken,
}
*s = append(*s, t)
return s
}
// Empty adds an empty item. Empty items render nothing but are followed by a
// separator in lists.
func Empty() *Statement {
return newStatement().Empty()
}
// Empty adds an empty item. Empty items render nothing but are followed by a
// separator in lists.
func (g *Group) Empty() *Statement {
s := Empty()
g.items = append(g.items, s)
return s
}
// Empty adds an empty item. Empty items render nothing but are followed by a
// separator in lists.
func (s *Statement) Empty() *Statement {
t := token{
typ: operatorToken,
content: "",
}
*s = append(*s, t)
return s
}
// Op renders the provided operator / token.
func Op(op string) *Statement {
return newStatement().Op(op)
}
// Op renders the provided operator / token.
func (g *Group) Op(op string) *Statement {
s := Op(op)
g.items = append(g.items, s)
return s
}
// Op renders the provided operator / token.
func (s *Statement) Op(op string) *Statement {
t := token{
typ: operatorToken,
content: op,
}
*s = append(*s, t)
return s
}
// Dot renders a period followed by an identifier. Use for fields and selectors.
func Dot(name string) *Statement {
// notest
// don't think this can be used in valid code?
return newStatement().Dot(name)
}
// Dot renders a period followed by an identifier. Use for fields and selectors.
func (g *Group) Dot(name string) *Statement {
// notest
// don't think this can be used in valid code?
s := Dot(name)
g.items = append(g.items, s)
return s
}
// Dot renders a period followed by an identifier. Use for fields and selectors.
func (s *Statement) Dot(name string) *Statement {
d := token{
typ: delimiterToken,
content: ".",
}
t := token{
typ: identifierToken,
content: name,
}
*s = append(*s, d, t)
return s
}
// Id renders an identifier.
func Id(name string) *Statement {
return newStatement().Id(name)
}
// Id renders an identifier.
func (g *Group) Id(name string) *Statement {
s := Id(name)
g.items = append(g.items, s)
return s
}
// Id renders an identifier.
func (s *Statement) Id(name string) *Statement {
t := token{
typ: identifierToken,
content: name,
}
*s = append(*s, t)
return s
}
// Qual renders a qualified identifier. Imports are automatically added when
// used with a File. If the path matches the local path, the package name is
// omitted. If package names conflict they are automatically renamed. Note that
// it is not possible to reliably determine the package name given an arbitrary
// package path, so a sensible name is guessed from the path and added as an
// alias. The names of all standard library packages are known so these do not
// need to be aliased. If more control is needed of the aliases, see
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
func Qual(path, name string) *Statement {
return newStatement().Qual(path, name)
}
// Qual renders a qualified identifier. Imports are automatically added when
// used with a File. If the path matches the local path, the package name is
// omitted. If package names conflict they are automatically renamed. Note that
// it is not possible to reliably determine the package name given an arbitrary
// package path, so a sensible name is guessed from the path and added as an
// alias. The names of all standard library packages are known so these do not
// need to be aliased. If more control is needed of the aliases, see
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
func (g *Group) Qual(path, name string) *Statement {
s := Qual(path, name)
g.items = append(g.items, s)
return s
}
// Qual renders a qualified identifier. Imports are automatically added when
// used with a File. If the path matches the local path, the package name is
// omitted. If package names conflict they are automatically renamed. Note that
// it is not possible to reliably determine the package name given an arbitrary
// package path, so a sensible name is guessed from the path and added as an
// alias. The names of all standard library packages are known so these do not
// need to be aliased. If more control is needed of the aliases, see
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
func (s *Statement) Qual(path, name string) *Statement {
g := &Group{
close: "",
items: []Code{
token{
typ: packageToken,
content: path,
},
token{
typ: identifierToken,
content: name,
},
},
name: "qual",
open: "",
separator: ".",
}
*s = append(*s, g)
return s
}
// Line inserts a blank line.
func Line() *Statement {
return newStatement().Line()
}
// Line inserts a blank line.
func (g *Group) Line() *Statement {
s := Line()
g.items = append(g.items, s)
return s
}
// Line inserts a blank line.
func (s *Statement) Line() *Statement {
t := token{
typ: layoutToken,
content: "\n",
}
*s = append(*s, t)
return s
}
+9
View File
@@ -0,0 +1,9 @@
// Package jennifer is a code generator for Go
package jennifer
//go:generate go get github.com/dave/jennifer/genjen
//go:generate genjen
//go:generate go get github.com/dave/jennifer/gennames
//go:generate gennames -output "jen/hints.go" -package "jen" -name "standardLibraryHints" -standard -novendor -path "./..."
//go:generate go get github.com/dave/rebecca/cmd/becca
//go:generate becca -package=github.com/dave/jennifer/jen