cmd/cue/cmd: add get to define CUE package from Go
Update #24
Change-Id: Ief47b7295249354d5eafbe0bcf33ea26dd6dfdc7
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1723
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/common_test.go b/cmd/cue/cmd/common_test.go
index fe71588..ff751cf 100644
--- a/cmd/cue/cmd/common_test.go
+++ b/cmd/cue/cmd/common_test.go
@@ -42,8 +42,8 @@
if err != nil {
log.Fatal(err)
}
-
const dir = "./testdata"
+
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
t.Run(path, func(t *testing.T) {
if err != nil {
diff --git a/cmd/cue/cmd/get.go b/cmd/cue/cmd/get.go
new file mode 100644
index 0000000..4222db7
--- /dev/null
+++ b/cmd/cue/cmd/get.go
@@ -0,0 +1,45 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+)
+
+// getCmd represents the extract command
+var getCmd = &cobra.Command{
+ Use: "get <language> [packages]",
+ Short: "add dependencies to the current module",
+ Long: `Get downloads packages or modules for CUE or another language
+to include them in the module's pkg directory.
+
+Get requires an additional language field to determine for which
+language definitions should be fetched. If get fetches definitions
+for a language other than CUE, the definitions are extracted from
+the source of the respective language and stored.
+The specifics on how dependencies are fechted and converted vary
+per language and are documented in the respective subcommands.
+`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ fmt.Println("get must be run as one of its subcommands")
+ return nil
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(getCmd)
+}
diff --git a/cmd/cue/cmd/get_go.go b/cmd/cue/cmd/get_go.go
new file mode 100644
index 0000000..0ab6262
--- /dev/null
+++ b/cmd/cue/cmd/get_go.go
@@ -0,0 +1,1073 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+
+ "cuelang.org/go/cue/format"
+ "cuelang.org/go/cue/parser"
+ cuetoken "cuelang.org/go/cue/token"
+ "github.com/spf13/cobra"
+ "golang.org/x/tools/go/packages"
+)
+
+// TODO:
+// Document:
+// - how to deal with "oneOf" or sum types?
+// - generate cue files for cuego definitions?
+// - cue go get or cue get go
+// - include generation report in doc_gen.cue or report.txt.
+// Possible enums:
+// package foo
+// Type: enumType
+
+var getGoCmd = &cobra.Command{
+ Use: "go [packages]",
+ Short: "add Go dependencies to the current module",
+ Long: `go converts Go types into CUE definitions
+
+The command "cue get go" is like "go get", but converts the retrieved Go
+packages to CUE. The retrieved packages are put in the CUE module's pkg
+directory at the import path of the corresponding Go package. The converted
+definitions are available to any CUE file within the CUE module by using
+this import path.
+
+The Go type definitions are converted to CUE based on how they would be
+interpreted by Go's encoding/json package. Definitions for a Go file foo.go
+are written to a CUE file named foo_go_gen.cue.
+
+It is safe for users to add additional files to the generated directories,
+as long as their name does not end with _gen.*.
+
+
+Rules of Converting Go types to CUE
+
+Go structs are converted to cue structs adhering to the following conventions:
+
+ - field names are translated based on the definition of a "json" or "yaml"
+ tag, in that order.
+
+ - embedded structs marked with a json inline tag unify with struct
+ definition. For instance, the Go struct
+
+ struct MyStruct {
+ Common ` + "json:\",inline\"" + `
+ Field string
+ }
+
+ translates to the CUE struct
+
+ MyStruct: Common & {
+ Field: string
+ }
+
+ - a type that implements MarshalJSON, UnmarshalJSON, MarshalYAML, or
+ UnmarshalYAML is translated to top (_) to indicate it may be any
+ value. For some Go core types for which the implementation of these
+ methods is known, like time.Time, the type may be more specific.
+
+ - a type implementing MarshalText or UnmarshalText is represented as
+ the CUE type string
+
+ - slices and arrays convert to CUE lists, except when the element type is
+ byte, in which case it translates to the CUE bytes type.
+ In the case of arrays, the length of the CUE value is constrained
+ accordingly, when possible.
+
+ - Maps translate to a CUE struct, where all elements are constrained to
+ be of Go map element type. Like for JSON, maps may only have string keys.
+
+ - Pointers translate to a sum type with the default value of null and
+ the Go type as an alternative value.
+
+ - Field tags are translated to CUE's field attributes. In some cases,
+ the contents are rewritten to reflect the corresponding types in CUE.
+ The @go attribute is added if the field name or type definition differs
+ between the generated CUE and the original Go.
+
+
+Native CUE Constraints
+
+Native CUE constraints may be defined in separate cue files alongside the
+generated files either in the original Go directory or in the generated
+directory. These files can impose additional constraints on types and values
+that are not otherwise expressible in Go. The package name for these CUE files
+must be the same as that of the Go package.
+
+For instance, for the type
+
+ package foo
+
+ type IP4String string
+
+defined in the Go package, one could add a cue file foo.cue with the following
+contents to allow IP4String to assume only valid IP4 addresses:
+
+ package foo
+
+ // IP4String defines a valid IP4 address.
+ IP4String: =~#"^\#(byte)\.\#(byte)\.\#(byte)\.\#(byte)$"#
+
+ // byte defines string allowing integer values of 0-255.
+ byte = #"([01]?\d?\d|2[0-4]\d|25[0-5])"#
+
+
+The "cue get go" command copies any cue files in the original Go package
+directory that has a package clause with the same name as the Go package to the
+destination directory, replacing its .cue ending with _gen.cue.
+
+Alternatively, the additional native constraints can be added to the generated
+package, as long as the file name does not end with _gen.cue.
+Running cue get go again to regenerate the package will never overwrite any
+files not ending with _gen.*.
+
+
+Constants and Enums
+
+Go does not have an enum or sum type. Conventionally, a type that is supposed
+to be an enum is followed by a const block with the allowed values for that
+type. However, as that is only a guideline and not a hard rule, these cases
+cannot be translated to CUE disjunctions automatically.
+
+Constant values, however, are generated in a way that makes it easy to convert
+a type to a proper enum using native CUE constraints. For instance, the Go type
+
+ package foo
+
+ type Switch int
+
+ const (
+ Off Switch = iota
+ On
+ )
+
+translates into the following CUE definitions:
+
+ package foo
+
+ Switch: int // enumSwitch
+
+ enumSwitch: Off | On
+
+ Off: 0
+ On: 1
+
+This definition allows any integer value for Switch, while the enumSwitch value
+defines all defined constants for Switch and thus all valid values if Switch
+were to be interpreted as an enum type. To turn Switch into an enum,
+include the following constraint in, say, enum.cue, in either the original
+source directory or the generated directory:
+
+ package foo
+
+ // limit the valid values for Switch to those existing as constants with
+ // the same type.
+ Switch: enumSwitch
+
+This tells CUE that only the values enumerated by enumSwitch are valid
+values for Switch. Note that there are now two definitions of Switch.
+CUE handles this in the usual way by unifying the two definitions, in which case
+the more restrictive enum interpretation of Switch remains.
+`,
+ // - TODO: interpret cuego's struct tags and annotations.
+
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return extract(cmd, args)
+ },
+}
+
+func init() {
+ getCmd.AddCommand(getGoCmd)
+
+ exclude = getGoCmd.Flags().StringP("exclude", "e", "",
+ "comma-separated list of regexps of entries")
+}
+
+var (
+ cueTestRoot string // the CUE module root for test purposes.
+ exclude *string
+
+ exclusions []*regexp.Regexp
+)
+
+type dstUsed struct {
+ dst string
+ used bool
+}
+
+func initExclusions() {
+ for _, re := range strings.Split(*exclude, ",") {
+ if re != "" {
+ exclusions = append(exclusions, regexp.MustCompile(re))
+ }
+ }
+}
+
+func filter(name string) bool {
+ if !ast.IsExported(name) {
+ return true
+ }
+ for _, ex := range exclusions {
+ if ex.MatchString(name) {
+ return true
+ }
+ }
+ return false
+}
+
+type extractor struct {
+ stderr io.Writer
+ err error
+ pkgs []*packages.Package
+ done map[string]bool
+
+ // per package
+ orig map[types.Type]*ast.StructType
+ usedPkgs map[string]bool
+
+ // per file
+ w *bytes.Buffer
+ cmap ast.CommentMap
+ pkg *packages.Package
+ consts map[string][]string
+ pkgNames map[string]string
+ usedInFile map[string]bool
+ indent int
+}
+
+func (e *extractor) logf(format string, args ...interface{}) {
+ if *fVerbose {
+ fmt.Fprintf(e.stderr, format+"\n", args...)
+ }
+}
+
+func (e *extractor) usedPkg(pkg string) {
+ e.usedPkgs[pkg] = true
+ e.usedInFile[pkg] = true
+}
+
+func (e *extractor) errorf(format string, args ...interface{}) {
+ err := fmt.Errorf(format, args...)
+ fmt.Fprintln(e.stderr, err)
+ if e.err == nil {
+ e.err = err
+ }
+}
+
+func initInterfaces() error {
+ cfg := &packages.Config{
+ Mode: packages.LoadAllSyntax,
+ }
+ p, err := packages.Load(cfg, "cuelang.org/go/cmd/cue/cmd/interfaces")
+ if err != nil {
+ return err
+ }
+
+ for e, tt := range p[0].TypesInfo.Types {
+ if n, ok := tt.Type.(*types.Named); ok && n.String() == "error" {
+ continue
+ }
+ if tt.Type.Underlying().String() == "interface{}" {
+ continue
+ }
+
+ switch tt.Type.Underlying().(type) {
+ case *types.Interface:
+ file := p[0].Fset.Position(e.Pos()).Filename
+ switch filepath.Base(file) {
+ case "top.go":
+ toTop = append(toTop, tt.Type)
+ case "text.go":
+ toString = append(toString, tt.Type)
+ }
+ }
+ }
+ return nil
+}
+
+var (
+ toTop []types.Type
+ toString []types.Type
+)
+
+// TODO:
+// - consider not including types with any dropped fields.
+
+func extract(cmd *cobra.Command, args []string) error {
+ // determine module root:
+ binst := loadFromArgs(cmd, []string{"."})[0]
+
+ if err := initInterfaces(); err != nil {
+ return err
+ }
+
+ // TODO: require explicitly set root.
+ root := binst.Root
+
+ // Override root in testing mode.
+ if cueTestRoot != "" {
+ root = cueTestRoot
+ }
+
+ cfg := &packages.Config{
+ Mode: packages.LoadAllSyntax,
+ }
+ pkgs, err := packages.Load(cfg, args...)
+ if err != nil {
+ return err
+ }
+
+ e := extractor{
+ stderr: cmd.OutOrStderr(),
+ pkgs: pkgs,
+ orig: map[types.Type]*ast.StructType{},
+ }
+
+ initExclusions()
+
+ e.done = map[string]bool{}
+
+ for _, p := range pkgs {
+ e.done[p.PkgPath] = true
+ }
+
+ for _, p := range pkgs {
+ if err := e.extractPkg(root, p); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (e *extractor) recordTypeInfo(p *packages.Package) {
+ for _, f := range p.Syntax {
+ ast.Inspect(f, func(n ast.Node) bool {
+ switch x := n.(type) {
+ case *ast.StructType:
+ e.orig[p.TypesInfo.TypeOf(x)] = x
+ }
+ return true
+ })
+ }
+}
+
+func (e *extractor) extractPkg(root string, p *packages.Package) error {
+ e.pkg = p
+ e.logf("--- Package %s", p.PkgPath)
+
+ e.recordTypeInfo(p)
+
+ e.consts = map[string][]string{}
+
+ for _, f := range p.Syntax {
+ for _, d := range f.Decls {
+ switch x := d.(type) {
+ case *ast.GenDecl:
+ e.recordConsts(x)
+ }
+ }
+ }
+
+ pkg := p.PkgPath
+ dir := filepath.Join(root, "pkg", filepath.FromSlash(pkg))
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+
+ e.usedPkgs = map[string]bool{}
+
+ args := pkg
+ if *exclude != "" {
+ args += " --exclude=" + *exclude
+ }
+
+ for i, f := range p.Syntax {
+ e.w = &bytes.Buffer{}
+
+ e.cmap = ast.NewCommentMap(p.Fset, f, f.Comments)
+
+ e.pkgNames = map[string]string{}
+ e.usedInFile = map[string]bool{}
+
+ for _, spec := range f.Imports {
+ key, _ := strconv.Unquote(spec.Path.Value)
+ if spec.Name != nil {
+ e.pkgNames[key] = spec.Name.Name
+ } else {
+ // TODO: incorrect, should be name of package clause
+ e.pkgNames[key] = path.Base(key)
+ }
+ }
+
+ hasEntries := false
+ for _, d := range f.Decls {
+ switch x := d.(type) {
+ case *ast.GenDecl:
+ if e.reportDecl(e.w, x) {
+ hasEntries = true
+ }
+ }
+ }
+
+ if !hasEntries && f.Doc == nil {
+ continue
+ }
+
+ pkgs := []string{}
+ for k := range e.usedInFile {
+ pkgs = append(pkgs, k)
+ }
+ sort.Strings(pkgs)
+
+ w := &bytes.Buffer{}
+
+ fmt.Fprintln(w, "// Code generated by cue get go. DO NOT EDIT.")
+ fmt.Fprintln(w)
+ fmt.Fprintln(w, "//cue:generate cue get go", args)
+ fmt.Fprintln(w)
+ if f.Doc != nil {
+ for _, c := range f.Doc.List {
+ fmt.Fprintln(w, c.Text)
+ }
+ }
+ fmt.Fprintf(w, "package %s\n", p.Name)
+ fmt.Fprintln(w)
+ if len(pkgs) > 0 {
+ fmt.Fprintln(w, "import (")
+ for _, s := range pkgs {
+ name := e.pkgNames[s]
+ if p.Imports[s].Name == name {
+ fmt.Fprintf(w, "%q\n", s)
+ } else {
+ fmt.Fprintf(w, "%v %q\n", name, s)
+ }
+ }
+ fmt.Fprintln(w, ")")
+ fmt.Fprintln(w)
+ }
+ fmt.Fprintln(w)
+ io.Copy(w, e.w)
+
+ file := filepath.Base(p.CompiledGoFiles[i])
+
+ file = strings.Replace(file, ".go", "_go", 1)
+ file += "_gen.cue"
+ b, err := format.Source(w.Bytes())
+ if err != nil {
+ ioutil.WriteFile(filepath.Join(dir, file), w.Bytes(), 0644)
+ fmt.Println(w.String())
+ fmt.Println(dir, file)
+ return err
+ }
+ err = ioutil.WriteFile(filepath.Join(dir, file), b, 0644)
+ if err != nil {
+ return err
+ }
+ }
+
+ for _, o := range p.CompiledGoFiles {
+ root := filepath.Dir(o)
+ err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
+ if fi.IsDir() && path != root {
+ return filepath.SkipDir
+ }
+ if filepath.Ext(path) != ".cue" {
+ return nil
+ }
+ f, err := parser.ParseFile(cuetoken.NewFileSet(), path, nil)
+ if err != nil {
+ return err
+ }
+
+ if f.Name != nil && f.Name.Name == p.Name {
+ file := filepath.Base(path)
+ file = file[:len(file)-len(".cue")]
+ file += "_gen.cue"
+
+ w := &bytes.Buffer{}
+ fmt.Fprintln(w, "// Code generated by cue get go. DO NOT EDIT.")
+ fmt.Fprintln(w)
+ fmt.Fprintln(w, "//cue:generate cue get go", args)
+ fmt.Fprintln(w)
+
+ b, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ w.Write(b)
+
+ dst := filepath.Join(dir, file)
+ if err := ioutil.WriteFile(dst, w.Bytes(), 0644); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ }
+
+ for path := range e.usedPkgs {
+ if !e.done[path] {
+ e.done[path] = true
+ p := p.Imports[path]
+ if err := e.extractPkg(root, p); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func (e *extractor) recordConsts(x *ast.GenDecl) {
+ if x.Tok != token.CONST {
+ return
+ }
+ for _, s := range x.Specs {
+ v, ok := s.(*ast.ValueSpec)
+ if !ok {
+ continue
+ }
+ for _, n := range v.Names {
+ typ := e.pkg.TypesInfo.TypeOf(n).String()
+ e.consts[typ] = append(e.consts[typ], n.Name)
+ }
+ }
+}
+
+func (e *extractor) reportDecl(w io.Writer, x *ast.GenDecl) (added bool) {
+ switch x.Tok {
+ case token.TYPE:
+ for _, s := range x.Specs {
+ v, ok := s.(*ast.TypeSpec)
+ if !ok || filter(v.Name.Name) {
+ continue
+ }
+
+ typ := e.pkg.TypesInfo.TypeOf(v.Name)
+ enums := e.consts[typ.String()]
+ name := v.Name.Name
+ switch tn, ok := e.pkg.TypesInfo.Defs[v.Name].(*types.TypeName); {
+ case ok:
+ if altType := e.altType(tn.Type()); altType != "" {
+ // TODO: add the underlying tag as a Go tag once we have
+ // proper string escaping for CUE.
+ e.printDoc(x.Doc, true)
+ fmt.Fprintf(e.w, "%s: %s", name, altType)
+ added = true
+ break
+ }
+ fallthrough
+
+ default:
+ if !supportedType(nil, typ) {
+ e.logf(" Dropped declaration %v of unsupported type %v", name, typ)
+ continue
+ }
+ added = true
+
+ if s := e.altType(types.NewPointer(typ)); s != "" {
+ e.printDoc(x.Doc, true)
+ fmt.Fprint(e.w, name, ": ", s)
+ break
+ }
+ // TODO: only print original type if value is not marked as enum.
+ underlying := e.pkg.TypesInfo.TypeOf(v.Type)
+ e.printField(name, false, underlying, x.Doc, true)
+ }
+
+ e.indent++
+ if len(enums) > 0 {
+ fmt.Fprintf(e.w, " // enum%s", name)
+
+ e.newLine()
+ e.newLine()
+ fmt.Fprintf(e.w, "enum%s:\n%v", name, enums[0])
+ for _, v := range enums[1:] {
+ fmt.Fprint(e.w, " |")
+ e.newLine()
+ fmt.Fprint(e.w, v)
+ }
+ }
+ e.indent--
+ e.newLine()
+ e.newLine()
+ }
+
+ case token.CONST:
+ // TODO: copy over comments for constant blocks.
+
+ for _, s := range x.Specs {
+ // TODO: determine type name and filter.
+ v, ok := s.(*ast.ValueSpec)
+ if !ok {
+ continue
+ }
+
+ for i, name := range v.Names {
+ if !ast.IsExported(name.Name) {
+ continue
+ }
+ added = true
+
+ e.printDoc(v.Doc, true)
+ fmt.Fprint(e.w, name.Name, ": ")
+
+ typ := e.pkg.TypesInfo.TypeOf(name)
+ if s := typ.String(); !strings.Contains(s, "untyped") {
+ switch s {
+ case "byte", "string", "error":
+ default:
+ e.printType(typ)
+ fmt.Fprint(e.w, " & ")
+ }
+ }
+
+ val := ""
+ comment := ""
+ if i < len(v.Values) {
+ if lit, ok := v.Values[i].(*ast.BasicLit); ok {
+ val = lit.Value
+ }
+ }
+
+ outer:
+ switch {
+ case len(val) <= 1:
+ case val[0] == '\'':
+ comment = " // " + val
+ val = ""
+
+ case strings.HasPrefix(val, "0"):
+ for _, c := range val[1:] {
+ if c < '0' || '9' < c {
+ val = ""
+ break outer
+ }
+ }
+ val = "0o" + val[1:]
+ }
+
+ if val == "" {
+ c := e.pkg.TypesInfo.Defs[v.Names[i]].(*types.Const)
+ val = c.Val().String()
+ }
+
+ fmt.Fprint(e.w, val, comment)
+ e.newLine()
+ }
+ }
+ e.newLine()
+ }
+ return added
+}
+
+func shortTypeName(t types.Type) string {
+ if n, ok := t.(*types.Named); ok {
+ return n.Obj().Name()
+ }
+ return t.String()
+}
+
+func (e *extractor) altType(typ types.Type) string {
+ ptr := types.NewPointer(typ)
+ for _, x := range toTop {
+ i := x.Underlying().(*types.Interface)
+ if types.Implements(typ, i) || types.Implements(ptr, i) {
+ t := shortTypeName(typ)
+ e.logf(" %v implements %s; setting type to _", t, x)
+ return "_"
+ }
+ }
+ for _, x := range toString {
+ i := x.Underlying().(*types.Interface)
+ if types.Implements(typ, i) || types.Implements(ptr, i) {
+ t := shortTypeName(typ)
+ e.logf(" %v implements %s; setting type to string", t, x)
+ return "string"
+ }
+ }
+ return ""
+}
+
+func (e *extractor) printDoc(doc *ast.CommentGroup, newline bool) {
+ if doc == nil {
+ return
+ }
+ if newline {
+ e.newLine()
+ }
+ for _, c := range doc.List {
+ fmt.Fprint(e.w, c.Text)
+ e.newLine()
+ }
+}
+
+func (e *extractor) newLine() {
+ fmt.Fprintln(e.w)
+ fmt.Fprint(e.w, strings.Repeat(" ", e.indent))
+}
+
+func supportedType(stack []types.Type, t types.Type) (ok bool) {
+ // handle recursive types
+ for _, t0 := range stack {
+ if t0 == t {
+ return true
+ }
+ }
+ stack = append(stack, t)
+
+ if named, ok := t.(*types.Named); ok {
+ obj := named.Obj()
+
+ // Redirect or drop Go standard library types.
+ if obj.Pkg() == nil {
+ // error interface
+ return true
+ }
+ switch obj.Pkg().Path() {
+ case "time":
+ switch named.Obj().Name() {
+ case "Time", "Duration", "Location", "Month", "Weekday":
+ return true
+ }
+ return false
+ case "math/big":
+ switch named.Obj().Name() {
+ case "Int", "Float":
+ return true
+ }
+ // case "net":
+ // // TODO: IP, Host, SRV, etc.
+ // case "url":
+ // // TODO: URL and Values
+ }
+ }
+
+ t = t.Underlying()
+ switch x := t.(type) {
+ case *types.Basic:
+ return x.String() != "invalid type"
+ case *types.Named:
+ return true
+ case *types.Pointer:
+ return supportedType(stack, x.Elem())
+ case *types.Slice:
+ return supportedType(stack, x.Elem())
+ case *types.Array:
+ return supportedType(stack, x.Elem())
+ case *types.Map:
+ if b, ok := x.Key().Underlying().(*types.Basic); !ok || b.Kind() != types.String {
+ return false
+ }
+ return supportedType(stack, x.Elem())
+ case *types.Struct:
+ // Eliminate structs with fields for which all fields are filtered.
+ if x.NumFields() == 0 {
+ return true
+ }
+ for i := 0; i < x.NumFields(); i++ {
+ f := x.Field(i)
+ if f.Exported() && supportedType(stack, f.Type()) {
+ return true
+ }
+ }
+ case *types.Interface:
+ return true
+ }
+ return false
+}
+
+func (e *extractor) printField(name string, opt bool, expr types.Type, doc *ast.CommentGroup, newline bool) (typename string) {
+ e.printDoc(doc, newline)
+ colon := ": "
+ if opt {
+ colon = "?: "
+ }
+ fmt.Fprint(e.w, name, colon)
+ pos := e.w.Len()
+ e.printType(expr)
+ return e.w.String()[pos:]
+}
+
+func (e *extractor) printType(expr types.Type) {
+ if x, ok := expr.(*types.Named); ok {
+ obj := x.Obj()
+ if obj.Pkg() == nil {
+ fmt.Fprint(e.w, "_")
+ return
+ }
+ // Check for builtin packages.
+ // TODO: replace these literal types with a reference to the fixed
+ // builtin type.
+ switch obj.Type().String() {
+ case "time.Time":
+ e.usedInFile["time"] = true
+ fmt.Fprint(e.w, e.pkgNames[obj.Pkg().Path()], ".", obj.Name())
+ return
+
+ case "math/big.Int":
+ fmt.Fprint(e.w, "int")
+ return
+
+ default:
+ if !strings.ContainsAny(obj.Pkg().Path(), ".") {
+ // Drop any standard library type if they haven't been handled
+ // above.
+ if s := e.altType(obj.Type()); s != "" {
+ fmt.Fprint(e.w, s)
+ return
+ }
+ }
+ }
+ if pkg := obj.Pkg(); pkg != nil {
+ if name := e.pkgNames[pkg.Path()]; name != "" {
+ fmt.Fprint(e.w, name, ".")
+ e.usedPkg(pkg.Path())
+ }
+ }
+ fmt.Fprint(e.w, obj.Name())
+ return
+ }
+
+ switch x := expr.(type) {
+ case *types.Pointer:
+ fmt.Fprintf(e.w, "null | ")
+ e.printType(x.Elem())
+
+ case *types.Struct:
+ for i := 0; i < x.NumFields(); i++ {
+ f := x.Field(i)
+ if f.Anonymous() && e.isInline(x.Tag(i)) {
+ typ := f.Type()
+ if _, ok := typ.(*types.Named); ok {
+ e.printType(typ)
+ fmt.Fprintf(e.w, " & ")
+ }
+ }
+ }
+ fmt.Fprint(e.w, "{")
+ e.indent++
+ e.printFields(x)
+ e.indent--
+ e.newLine()
+ fmt.Fprint(e.w, "}")
+
+ case *types.Slice:
+ // TODO: should this be x.Elem().Underlying().String()? One could
+ // argue either way.
+ if x.Elem().String() == "byte" {
+ fmt.Fprint(e.w, "bytes")
+ } else {
+ fmt.Fprint(e.w, "[...")
+ e.printType(x.Elem())
+ fmt.Fprint(e.w, "]")
+ }
+
+ case *types.Array:
+ if x.Elem().String() == "byte" {
+ // TODO: no way to constraint lengths of bytes for now, as regexps
+ // operate on Unicode, not bytes. So we need
+ // fmt.Fprint(e.w, fmt.Sprintf("=~ '^\C{%d}$'", x.Len())),
+ // but regexp does not support that.
+ // But translate to bytes, instead of [...byte] to be consistent.
+ fmt.Fprint(e.w, "bytes")
+ } else {
+ fmt.Fprintf(e.w, "%d*[", x.Len())
+ e.printType(x.Elem())
+ fmt.Fprint(e.w, "]")
+ }
+
+ case *types.Map:
+ if b, ok := x.Key().Underlying().(*types.Basic); !ok || b.Kind() != types.String {
+ log.Panicf("unsupported map key type %T", x.Key())
+ }
+ fmt.Fprintf(e.w, "{ <_>: ")
+ e.printType(x.Elem())
+ fmt.Fprintf(e.w, " }")
+
+ case *types.Basic:
+ fmt.Fprint(e.w, x.String())
+
+ case *types.Interface:
+ fmt.Fprintf(e.w, "_")
+
+ default:
+ // record error
+ panic(fmt.Sprintf("unsupported type %T", x))
+ }
+}
+
+func (e *extractor) printFields(x *types.Struct) {
+ s := e.orig[x]
+ docs := []*ast.CommentGroup{}
+ for _, f := range s.Fields.List {
+ if len(f.Names) == 0 {
+ docs = append(docs, f.Doc)
+ } else {
+ for range f.Names {
+ docs = append(docs, f.Doc)
+ }
+ }
+ }
+ count := 0
+ for i := 0; i < x.NumFields(); i++ {
+ f := x.Field(i)
+ if !ast.IsExported(f.Name()) {
+ continue
+ }
+ if !supportedType(nil, f.Type()) {
+ e.logf(" Dropped field %v for unsupported type %v", f.Name(), f.Type())
+ continue
+ }
+ if f.Anonymous() && e.isInline(x.Tag(i)) {
+ typ := f.Type()
+ if _, ok := typ.(*types.Named); !ok {
+ switch x := typ.(type) {
+ case *types.Struct:
+ e.printFields(x)
+ default:
+ panic(fmt.Sprintf("unimplemented embedding for type %T", x))
+ }
+ }
+ continue
+ }
+ tag := x.Tag(i)
+ name := getName(f.Name(), tag)
+ if name == "-" {
+ continue
+ }
+ e.newLine()
+ cueType := e.printField(name, e.isOptional(tag), f.Type(), docs[i], count > 0)
+
+ // Add field tag to convert back to Go.
+ typeName := f.Type().String()
+ // simplify type names:
+ for path, name := range e.pkgNames {
+ typeName = strings.Replace(typeName, path+".", name+".", -1)
+ }
+ typeName = strings.Replace(typeName, e.pkg.Types.Path()+".", "", -1)
+
+ // TODO: remove fields in @go attr that are the same as printed?
+ if name != f.Name() || typeName != cueType {
+ fmt.Fprint(e.w, "@go(")
+ if name != f.Name() {
+ fmt.Fprint(e.w, f.Name())
+ }
+ if typeName != cueType {
+ if strings.ContainsAny(typeName, `#"',()=`) {
+ typeName = strconv.Quote(typeName)
+ }
+ fmt.Fprint(e.w, ",", typeName)
+ }
+ fmt.Fprintf(e.w, ")")
+ }
+
+ // Carry over protobuf field tags with modifications.
+ if t := reflect.StructTag(tag).Get("protobuf"); t != "" {
+ split := strings.Split(t, ",")
+ k := 0
+ for _, s := range split {
+ if strings.HasPrefix(s, "name=") && s[len("name="):] == name {
+ continue
+ }
+ split[k] = s
+ k++
+ }
+ split = split[:k]
+
+ // Put tag first, as type could potentially be elided and is
+ // "more optional".
+ if len(split) >= 2 {
+ split[0], split[1] = split[1], split[0]
+ }
+ fmt.Fprintf(e.w, " @protobuf(%s)", strings.Join(split, ","))
+ }
+
+ // Carry over XML tags.
+ if t := reflect.StructTag(tag).Get("xml"); t != "" {
+ fmt.Fprintf(e.w, " @xml(%s)", t)
+ }
+
+ // Carry over TOML tags.
+ if t := reflect.StructTag(tag).Get("toml"); t != "" {
+ fmt.Fprintf(e.w, " @toml(%s)", t)
+ }
+
+ // TODO: should we in general carry over any unknown tag verbatim?
+
+ count++
+ }
+}
+
+func (e *extractor) isInline(tag string) bool {
+ return hasFlag(tag, "json", "inline", 1) ||
+ hasFlag(tag, "yaml", "inline", 1)
+}
+
+func (e *extractor) isOptional(tag string) bool {
+ // TODO: also when the type is a list or other kind of pointer.
+ return hasFlag(tag, "json", "omitempty", 1) ||
+ hasFlag(tag, "yaml", "omitempty", 1)
+}
+
+func hasFlag(tag, key, flag string, offset int) bool {
+ if t := reflect.StructTag(tag).Get(key); t != "" {
+ split := strings.Split(t, ",")
+ if offset >= len(split) {
+ return false
+ }
+ for _, str := range split[offset:] {
+ if str == flag {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func getName(name string, tag string) string {
+ tags := reflect.StructTag(tag)
+ for _, s := range []string{"json", "yaml"} {
+ if tag, ok := tags.Lookup(s); ok {
+ if p := strings.Index(tag, ","); p >= 0 {
+ tag = tag[:p]
+ }
+ if tag != "" {
+ return tag
+ }
+ }
+ }
+ // TODO: should we also consider to protobuf name? Probably not.
+
+ return name
+}
diff --git a/cmd/cue/cmd/get_go_test.go b/cmd/cue/cmd/get_go_test.go
new file mode 100644
index 0000000..36b1884
--- /dev/null
+++ b/cmd/cue/cmd/get_go_test.go
@@ -0,0 +1,102 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/retr0h/go-gilt/copy"
+ "github.com/spf13/cobra"
+)
+
+func TestGetGo(t *testing.T) {
+ // Leave the current working directory outside the testdata directory
+ // so that Go loader finds the Go mod file and creates a proper path.
+ // We need to trick the command to generate the data within the testdata
+ // directory, though.
+ tmp, err := ioutil.TempDir("", "cue_get_go")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmp)
+
+ cueTestRoot = tmp
+
+ // We don't use runCommand here, as we are interested in generated packages.
+ cmd := &cobra.Command{RunE: getGoCmd.RunE}
+ cmd.SetArgs([]string{"./testdata/code/go/..."})
+ err = cmd.Execute()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Packages will generate differently in modules versus GOPATH. Search
+ // for the common ground to not have breaking text if people run these
+ // test in GOPATH mode.
+ root := ""
+ filepath.Walk(tmp, func(path string, info os.FileInfo, err error) error {
+ if root != "" {
+ return filepath.SkipDir
+ }
+ if filepath.Base(path) == "cuelang.org" {
+ root = filepath.Dir(path)
+ return filepath.SkipDir
+ }
+ return nil
+ })
+
+ const dst = "testdata/pkg"
+
+ if *update {
+ os.RemoveAll(dst)
+ err := copy.Dir(filepath.Join(root), dst)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Skip("files updated")
+ }
+
+ prefix := "testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/"
+ filepath.Walk(dst, func(path string, info os.FileInfo, err error) error {
+ if info.IsDir() {
+ return nil
+ }
+ t.Run(path, func(t *testing.T) {
+ want := loadFile(t, path)
+ got := loadFile(t, filepath.Join(root, path[len(dst):]))
+
+ if want != got {
+ t.Errorf("contexts for file %s differ", path[len(prefix):])
+ }
+ })
+ return nil
+ })
+}
+
+func loadFile(t *testing.T, path string) string {
+ t.Helper()
+ b, err := ioutil.ReadFile(path)
+ if err != nil {
+ t.Fatalf("could not load file %s", path)
+ }
+ // Strip comments up till package clause. Local packages will generate
+ // differently using GOPATH versuse modules.
+ s := string(b)
+ return s[strings.Index(s, "package"):]
+}
diff --git a/cmd/cue/cmd/interfaces/text.go b/cmd/cue/cmd/interfaces/text.go
new file mode 100644
index 0000000..0aa0270
--- /dev/null
+++ b/cmd/cue/cmd/interfaces/text.go
@@ -0,0 +1,22 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package interfaces
+
+import "encoding"
+
+type (
+ textMarshaler = encoding.TextMarshaler
+ textUnmarshaler = encoding.TextUnmarshaler
+)
diff --git a/cmd/cue/cmd/interfaces/top.go b/cmd/cue/cmd/interfaces/top.go
new file mode 100644
index 0000000..0d2d9a5
--- /dev/null
+++ b/cmd/cue/cmd/interfaces/top.go
@@ -0,0 +1,29 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package interfaces
+
+import (
+ "encoding/json"
+)
+
+type (
+ jsonMarshaler = json.Marshaler
+ jsonUnmarshaler = json.Unmarshaler
+
+ yamlMarshal interface{ MarshalYAML() (interface{}, error) }
+ yamlUnmarshal interface {
+ UnmarshalYAML(func(interface{}) error) error
+ }
+)
diff --git a/cmd/cue/cmd/root.go b/cmd/cue/cmd/root.go
index a705c0c..ad6528e 100644
--- a/cmd/cue/cmd/root.go
+++ b/cmd/cue/cmd/root.go
@@ -28,7 +28,7 @@
// fix: rewrite/refactor configuration files
// -i interactive: open diff and ask to update
// serve: like cmd, but for servers
-// extract: extract cue from other languages, like proto and go.
+// get: convert cue from other languages, like proto and go.
// gen: generate files for other languages
// generate like go generate (also convert cue to go doc)
// test load and fully evaluate test files.
@@ -151,13 +151,20 @@
}
var (
- fDebug = rootCmd.PersistentFlags().Bool("debug", false, "give detailed error info")
- fTrace = rootCmd.PersistentFlags().Bool("trace", false, "trace computation")
- fDryrun = rootCmd.PersistentFlags().BoolP("dryrun", "n", false, "only run simulation")
- fPackage = rootCmd.PersistentFlags().StringP("package", "p", "", "CUE package to evaluate")
- fSimplify = rootCmd.PersistentFlags().BoolP("simplify", "s", false, "simplify output")
- fIgnore = rootCmd.PersistentFlags().BoolP("ignore", "i", false, "proceed in the presence of errors")
- fVerbose = rootCmd.PersistentFlags().BoolP("verbose", "v", false, "print information about progress")
+ fDebug = rootCmd.PersistentFlags().Bool("debug", false,
+ "give detailed error info")
+ fTrace = rootCmd.PersistentFlags().Bool("trace", false,
+ "trace computation")
+ fDryrun = rootCmd.PersistentFlags().BoolP("dryrun", "n", false,
+ "only run simulation")
+ fPackage = rootCmd.PersistentFlags().StringP("package", "p", "",
+ "CUE package to evaluate")
+ fSimplify = rootCmd.PersistentFlags().BoolP("simplify", "s", false,
+ "simplify output")
+ fIgnore = rootCmd.PersistentFlags().BoolP("ignore", "i", false,
+ "proceed in the presence of errors")
+ fVerbose = rootCmd.PersistentFlags().BoolP("verbose", "v", false,
+ "print information about progress")
)
// initConfig reads in config file and ENV variables if set.
diff --git a/cmd/cue/cmd/testdata/code/go/pkg1/file1.go b/cmd/cue/cmd/testdata/code/go/pkg1/file1.go
new file mode 100644
index 0000000..eaa4f2e
--- /dev/null
+++ b/cmd/cue/cmd/testdata/code/go/pkg1/file1.go
@@ -0,0 +1,104 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package pkg1
+
+import (
+ "encoding"
+ "encoding/json"
+ "time"
+
+ p2 "cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2"
+)
+
+// Foozer foozes a jaman.
+type Foozer struct {
+ Int int
+ String string
+
+ Inline `json:",inline"`
+ NoInline
+
+ CustomJSON CustomJSON
+ CustomYAML *CustomYAML
+ AnyJSON json.Marshaler
+ AnyText encoding.TextMarshaler
+
+ Bar int `json:"bar,omitempty"`
+
+ exclude int
+
+ // Time is mapped to CUE's internal type.
+ Time time.Time
+
+ Barzer p2.Barzer
+
+ Map map[string]*CustomJSON
+ Slice1 []int
+ Slice2 []interface{}
+ Slice3 *[]json.Unmarshaler
+ Array1 [5]int
+ Array2 [5]interface{}
+ Array3 *[5]json.Marshaler
+
+ Intf Interface `protobuf:"varint,2,name=intf"`
+ Intf2 interface{}
+ Intf3 struct{ Interface }
+ Intf4 interface{ Foo() }
+
+ // Even though this struct as a type implements MarshalJSON, it is known
+ // that it is really only implemented by the embedded field.
+ Embed struct{ CustomJSON }
+
+ Unsupported map[int]string
+}
+
+// Level gives an indication of the extent of stuff.
+type Level int
+
+const (
+ Unknown Level = iota
+ Low
+ // Medium is neither High nor Low
+ Medium
+ High
+)
+
+type CustomJSON struct {
+}
+
+func (c *CustomJSON) MarshalJSON() ([]byte, error) {
+ return nil, nil
+}
+
+type CustomYAML struct {
+}
+
+func (c CustomYAML) MarshalYAML() ([]byte, error) {
+ return nil, nil
+}
+
+type excludeType int
+
+type Inline struct {
+ Kind string
+}
+
+type NoInline struct {
+ Kind string
+}
+
+type Interface interface {
+ Boomer() bool
+}
diff --git a/cmd/cue/cmd/testdata/code/go/pkg2/add.cue b/cmd/cue/cmd/testdata/code/go/pkg2/add.cue
new file mode 100644
index 0000000..e1721e8
--- /dev/null
+++ b/cmd/cue/cmd/testdata/code/go/pkg2/add.cue
@@ -0,0 +1,19 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package pkg2
+
+Barzer: {
+ S: =~"cat$"
+}
diff --git a/cmd/cue/cmd/testdata/code/go/pkg2/pkg2.go b/cmd/cue/cmd/testdata/code/go/pkg2/pkg2.go
new file mode 100644
index 0000000..42195d8
--- /dev/null
+++ b/cmd/cue/cmd/testdata/code/go/pkg2/pkg2.go
@@ -0,0 +1,42 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package pkg2 does other stuff.
+package pkg2
+
+import (
+ "math/big"
+ t "time"
+)
+
+// A Barzer barzes.
+type Barzer struct {
+ A int `protobuf:"varint,2," json:"a"`
+
+ T t.Time
+ B *big.Int
+ C big.Int
+ F big.Float `xml:",attr"`
+ G *big.Float
+ H bool `json:"-"`
+ S string
+
+ Err error
+}
+
+const Perm = 0755
+
+const Few = 3
+
+const Couple int = 2
diff --git a/cmd/cue/cmd/testdata/cue.mod b/cmd/cue/cmd/testdata/cue.mod
new file mode 100644
index 0000000..ab9fbdd
--- /dev/null
+++ b/cmd/cue/cmd/testdata/cue.mod
@@ -0,0 +1,14 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
diff --git a/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg1/file1_go_gen.cue b/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg1/file1_go_gen.cue
new file mode 100644
index 0000000..cd9b143
--- /dev/null
+++ b/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg1/file1_go_gen.cue
@@ -0,0 +1,76 @@
+// Code generated by cue get go. DO NOT EDIT.
+
+//cue:generate cue get go cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg1
+
+package pkg1
+
+import (
+ p2 "cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2"
+ "time"
+)
+
+// Foozer foozes a jaman.
+Foozer: Inline & {
+ Int: int
+ String: string
+ NoInline: NoInline
+ CustomJSON: CustomJSON
+ CustomYAML: null | CustomYAML @go(,*CustomYAML)
+ AnyJSON: _ @go(,json.Marshaler)
+ AnyText: string @go(,encoding.TextMarshaler)
+ bar?: int @go(Bar)
+
+ // Time is mapped to CUE's internal type.
+ Time: time.Time
+ Barzer: p2.Barzer
+ Map: {<_>: null | CustomJSON} @go(,map[string]*CustomJSON)
+ Slice1: [...int] @go(,[]int)
+ Slice2: [...] @go(,[]interface{})
+ Slice3: null | [...] @go(,*[]json.Unmarshaler)
+ Array1: 5 * [int] @go(,[5]int)
+ Array2: 5 * [_] @go(,[5]interface{})
+ Array3: null | 5*[_] @go(,*[5]json.Marshaler)
+ Intf: Interface @protobuf(2,varint,name=intf)
+ Intf2: _ @go(,interface{})
+ Intf3: {
+ Interface: Interface
+ } @go(,struct{Interface})
+ Intf4: _ @go(,"interface{Foo()}")
+
+ // Even though this struct as a type implements MarshalJSON, it is known
+ // that it is really only implemented by the embedded field.
+ Embed: {
+ CustomJSON: CustomJSON
+ } @go(,struct{CustomJSON})
+}
+
+// Level gives an indication of the extent of stuff.
+Level: int // enumLevel
+
+enumLevel:
+ Unknown |
+ Low |
+ Medium |
+ High
+
+Unknown: Level & 0
+Low: Level & 1
+
+// Medium is neither High nor Low
+Medium: Level & 2
+High: Level & 3
+
+CustomJSON: _
+
+CustomYAML: {
+}
+
+Inline: {
+ Kind: string
+}
+
+NoInline: {
+ Kind: string
+}
+
+Interface: _
diff --git a/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/add_gen.cue b/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/add_gen.cue
new file mode 100644
index 0000000..9cb7264
--- /dev/null
+++ b/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/add_gen.cue
@@ -0,0 +1,23 @@
+// Code generated by cue get go. DO NOT EDIT.
+
+//cue:generate cue get go cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2
+
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package pkg2
+
+Barzer: {
+ S: =~"cat$"
+}
diff --git a/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/pkg2_go_gen.cue b/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/pkg2_go_gen.cue
new file mode 100644
index 0000000..1cebd7c
--- /dev/null
+++ b/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/pkg2_go_gen.cue
@@ -0,0 +1,28 @@
+// Code generated by cue get go. DO NOT EDIT.
+
+//cue:generate cue get go cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2
+
+// Package pkg2 does other stuff.
+package pkg2
+
+import (
+ t "time"
+)
+
+// A Barzer barzes.
+Barzer: {
+ a: int @go(A) @protobuf(2,varint,)
+ T: t.Time
+ B: null | int @go(,*big.Int)
+ C: int @go(,big.Int)
+ F: string @go(,big.Float) @xml(,attr)
+ G: null | string @go(,*big.Float)
+ S: string
+ Err: _ @go(,error)
+}
+
+Perm: 0o755
+
+Few: 3
+
+Couple: int & 2
diff --git a/go.mod b/go.mod
index 00ad7ec..eb5f5ea 100644
--- a/go.mod
+++ b/go.mod
@@ -5,10 +5,10 @@
github.com/cockroachdb/apd v1.1.0
github.com/ghodss/yaml v1.0.0
github.com/google/go-cmp v0.2.0
- github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/lib/pq v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.0.0
github.com/pkg/errors v0.8.0 // indirect
+ github.com/retr0h/go-gilt v0.0.0-20190206215556-f73826b37af2
github.com/spf13/cobra v0.0.3
github.com/spf13/viper v1.3.1
golang.org/x/exp/errors v0.0.0-20181221233300-b68661188fbf
diff --git a/go.sum b/go.sum
index 2aa9834..b88224a 100644
--- a/go.sum
+++ b/go.sum
@@ -6,6 +6,7 @@
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
@@ -18,8 +19,10 @@
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/logrusorgru/aurora v0.0.0-20180419164547-d694e6f975a9/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
@@ -32,36 +35,47 @@
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/retr0h/go-gilt v0.0.0-20190206215556-f73826b37af2 h1:vZ42M1tDiMLtirFA1K5k2QVFhWRqR4BjdSw0IMclzH4=
+github.com/retr0h/go-gilt v0.0.0-20190206215556-f73826b37af2/go.mod h1:7PJr6TwZ6FwyTMn8zrm5QbMfH7yCiv56Wi9hRPhpWSM=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/xeipuuv/gojsonpointer v0.0.0-20170225233418-6fe8760cad35/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v0.0.0-20171230112544-511d08a359d1/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/exp/errors v0.0.0-20181221233300-b68661188fbf h1:4SQtY0VxhI0RZe/PFmCCfHyaPVuC5DgyXEqehsAWjwc=
golang.org/x/exp/errors v0.0.0-20181221233300-b68661188fbf/go.mod h1:YgqsNsAu4fTvlab/7uiYK9LJrCIzKg/NiZUIH1/ayqo=
+golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20181018182439-def26773749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181210225255-6a3e9aa2ab77 h1:s+6psEFi3o1QryeA/qyvUoVaHMCQkYVvZ0i2ZolwSJc=
golang.org/x/tools v0.0.0-20181210225255-6a3e9aa2ab77/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1VAMZmcIWyFz7QCMSIIa3Bg=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=