cmd/cue/cmd: support openapi out and proto in

- Adds general support for non-CUE schema

Issue #56

Change-Id: I6ca639b2e0703cb2120f7661af24ee3111cdfe54
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5262
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index e0cf5c5..4e04652 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -405,6 +405,21 @@
 					"unsupported encoding %q", f.Encoding)
 			}
 		}
+
+		switch {
+		case len(p.insts) > 0 && len(p.orphanedSchema) > 0:
+			return nil, errors.Newf(token.NoPos,
+				"cannot define packages and schema")
+		case len(p.orphanedData) > 0 && len(p.orphanedSchema) > 1:
+			// TODO: allow this when schema have ID specified.
+			return nil, errors.Newf(token.NoPos,
+				"cannot define data with more than one schema")
+		case len(p.orphanedData) > 0 && len(p.orphanedSchema) == 1:
+			b.BuildFiles = append(b.BuildFiles, p.orphanedSchema...)
+			p.insts = append(p.insts, b)
+		case len(p.orphanedSchema) > 0:
+			p.orphanedData = p.orphanedSchema
+		}
 	}
 
 	if len(p.expressions) > 1 {
@@ -449,6 +464,7 @@
 		Stdout:    b.cmd.OutOrStdout(),
 		ProtoPath: flagProtoPath.StringArray(b.cmd),
 		AllErrors: flagAllErrors.Bool(b.cmd),
+		PkgName:   flagPackage.String(b.cmd),
 	}
 	return nil
 }
diff --git a/cmd/cue/cmd/orphans.go b/cmd/cue/cmd/orphans.go
index 02b35d3..8687f8e 100644
--- a/cmd/cue/cmd/orphans.go
+++ b/cmd/cue/cmd/orphans.go
@@ -23,6 +23,7 @@
 
 	"cuelang.org/go/cue"
 	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/ast/astutil"
 	"cuelang.org/go/cue/build"
 	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/cue/token"
@@ -38,7 +39,6 @@
 		useList    = flagList.Bool(b.cmd)
 		path       = flagPath.StringArray(b.cmd)
 		useContext = flagWithContext.Bool(b.cmd)
-		pkg        = flagPackage.String(b.cmd)
 		match      = flagGlob.String(b.cmd)
 	)
 	if !b.forceOrphanProcessing && !perFile && !useList && len(path) == 0 {
@@ -51,6 +51,7 @@
 		return false, err
 	}
 
+	pkg := b.encConfig.PkgName
 	if pkg == "" {
 		pkg = i.PkgName
 	} else if pkg != "" && i.PkgName != "" && i.PkgName != pkg && !flagForce.Bool(b.cmd) {
@@ -75,16 +76,13 @@
 		d := encoding.NewDecoder(f, b.encConfig)
 		defer d.Close()
 
-		var objs []ast.Expr
+		var objs []*ast.File
 
 		for ; !d.Done(); d.Next() {
-			if expr := d.Expr(); expr != nil {
+			if expr := d.File(); expr != nil {
 				objs = append(objs, expr)
 				continue
 			}
-			f := d.File()
-			f.Filename = newName(d.Filename(), d.Index())
-			files = append(files, f)
 		}
 
 		if perFile {
@@ -126,13 +124,30 @@
 	return true, nil
 }
 
-func placeOrphans(cmd *Command, filename, pkg string, objs ...ast.Expr) (*ast.File, error) {
+func toExpr(f *ast.File) (expr ast.Expr, pkg *ast.Package) {
+	var p int
+outer:
+	for i, d := range f.Decls {
+		switch x := d.(type) {
+		case *ast.Package:
+			pkg = x
+		case *ast.ImportDecl:
+			p = i + 1
+		case *ast.CommentGroup:
+		default:
+			break outer
+		}
+	}
+	return &ast.StructLit{Elts: f.Decls[p:]}, pkg
+}
+
+func placeOrphans(cmd *Command, filename, pkg string, objs ...*ast.File) (*ast.File, error) {
 	f := &ast.File{}
 
 	index := newIndex()
-	for i, expr := range objs {
+	for i, file := range objs {
+		expr, p := toExpr(file)
 
-		// Compute a path different from root.
 		var pathElems []ast.Label
 		var pathTokens []token.Token
 
@@ -200,6 +215,9 @@
 			field := &ast.Field{Label: pathElems[0]}
 			field.Token = pathTokens[0]
 			f.Decls = append(f.Decls, field)
+			if p != nil {
+				astutil.CopyComments(field, p)
+			}
 			for i, e := range pathElems[1:] {
 				newField := &ast.Field{Label: e}
 				newVal := ast.NewStruct(newField)
diff --git a/cmd/cue/cmd/testdata/script/def_openapi.txt b/cmd/cue/cmd/testdata/script/def_openapi.txt
new file mode 100644
index 0000000..cf62033
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/def_openapi.txt
@@ -0,0 +1,105 @@
+cue def foo.cue -o openapi:-
+cmp stdout expect-json
+
+cue def foo.cue -o openapi+cue:-
+cmp stdout expect-cue
+
+cue def foo.cue -o openapi+yaml:-
+cmp stdout expect-yaml
+
+-- foo.cue --
+Foo :: {
+    a: int
+    b: uint & <10
+}
+
+Bar :: {
+    foo: Foo
+}
+
+-- expect-json --
+{
+    "openapi": "3.0.0",
+    "info": {},
+    "paths": {},
+    "components": {
+        "schemas": {
+            "Foo": {
+                "type": "object",
+                "required": [
+                    "a",
+                    "b"
+                ],
+                "properties": {
+                    "a": {
+                        "type": "integer"
+                    },
+                    "b": {
+                        "type": "integer",
+                        "minimum": 0,
+                        "exclusiveMaximum": 10
+                    }
+                }
+            },
+            "Bar": {
+                "type": "object",
+                "required": [
+                    "foo"
+                ],
+                "properties": {
+                    "foo": {
+                        "$ref": "#/components/schemas/Foo"
+                    }
+                }
+            }
+        }
+    }
+}
+-- expect-cue --
+openapi: "3.0.0"
+info: {}
+paths: {}
+components: schemas: {
+	Foo: {
+		type: "object"
+		required: ["a", "b"]
+		properties: {
+			a: type: "integer"
+			b: {
+				type:             "integer"
+				minimum:          0
+				exclusiveMaximum: 10
+			}
+		}
+	}
+	Bar: {
+		type: "object"
+		required: ["foo"]
+		properties: foo: $ref: "#/components/schemas/Foo"
+	}
+}
+-- expect-yaml --
+openapi: 3.0.0
+info: {}
+paths: {}
+components:
+    schemas:
+        Foo:
+            type: object
+            required:
+              - a
+              - b
+            properties:
+                a:
+                    type: integer
+                b:
+                    type: integer
+                    minimum: 0
+                    exclusiveMaximum: 10
+        Bar:
+            type: object
+            required:
+              - foo
+            properties:
+                foo:
+                    $ref: '#/components/schemas/Foo'
diff --git a/cmd/cue/cmd/testdata/script/def_proto.txt b/cmd/cue/cmd/testdata/script/def_proto.txt
new file mode 100644
index 0000000..59fc070
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/def_proto.txt
@@ -0,0 +1,225 @@
+cue def policy.proto -p api -I include
+cmp stdout expect-stdout
+
+-- expect-stdout --
+// policy_gen.cue
+package api
+
+import "time"
+
+// Attributes defines attributes.
+Attributes: {
+	// A map of attribute name to its value.
+	attributes?: {
+		[string]: Attributes_AttributeValue
+	} @protobuf(1,type=map<string,AttributeValue>)
+}
+
+// Specifies one attribute value with different type.
+Attributes_AttributeValue: {
+}
+// The attribute value.
+Attributes_AttributeValue: {
+	stringValue: string @protobuf(2,name=string_value)
+} | {
+	int64Value: int64 @protobuf(3,name=int64_value)
+} | {
+	doubleValue: float64 @protobuf(4,type=double,name=double_value)
+} | {
+	boolValue: bool @protobuf(5,name=bool_value)
+} | {
+	bytesValue: bytes @protobuf(6,name=bytes_value)
+} | {
+	timestampValue: time.Time @protobuf(7,type=google.protobuf.Timestamp,name=timestamp_value)
+} | {
+	// Used for values of type STRING_MAP
+	stringMapValue: Attributes_StringMap @protobuf(9,type=StringMap,name=string_map_value)
+}
+
+// Defines a string map.
+Attributes_StringMap: {
+	// Holds a set of name/value pairs.
+	entries?: {
+		[string]: string
+	} @protobuf(1,type=map<string,string>)
+}
+-- policy.proto --
+
+syntax = "proto3";
+
+import "google/protobuf/timestamp.proto";
+
+package acme.api.v1;
+
+option go_package = "acme.com/api/v1";
+
+
+// Attributes defines attributes.
+message Attributes {
+  // A map of attribute name to its value.
+  map<string, AttributeValue> attributes = 1;
+
+  // Specifies one attribute value with different type.
+  message AttributeValue {
+    // The attribute value.
+    oneof value {
+      string string_value = 2;
+      int64 int64_value = 3;
+      double double_value = 4;
+      bool bool_value = 5;
+      bytes bytes_value = 6;
+      google.protobuf.Timestamp timestamp_value = 7;
+
+      // Used for values of type STRING_MAP
+      StringMap string_map_value = 9;
+    }
+  }
+
+  // Defines a string map.
+  message StringMap {
+    // Holds a set of name/value pairs.
+    map<string, string> entries = 1;
+  }
+}
+
+-- include/google/protobuf/timestamp.proto --
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/timestamp";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TimestampProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Timestamp represents a point in time independent of any time zone or local
+// calendar, encoded as a count of seconds and fractions of seconds at
+// nanosecond resolution. The count is relative to an epoch at UTC midnight on
+// January 1, 1970, in the proleptic Gregorian calendar which extends the
+// Gregorian calendar backwards to year one.
+//
+// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+// second table is needed for interpretation, using a [24-hour linear
+// smear](https://developers.google.com/time/smear).
+//
+// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+// restricting to that range, we ensure that we can convert to and from [RFC
+// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+//
+// # Examples
+//
+// Example 1: Compute Timestamp from POSIX `time()`.
+//
+//     Timestamp timestamp;
+//     timestamp.set_seconds(time(NULL));
+//     timestamp.set_nanos(0);
+//
+// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+//
+//     struct timeval tv;
+//     gettimeofday(&tv, NULL);
+//
+//     Timestamp timestamp;
+//     timestamp.set_seconds(tv.tv_sec);
+//     timestamp.set_nanos(tv.tv_usec * 1000);
+//
+// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+//
+//     FILETIME ft;
+//     GetSystemTimeAsFileTime(&ft);
+//     UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+//
+//     // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+//     // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+//     Timestamp timestamp;
+//     timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+//     timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+//
+// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+//
+//     long millis = System.currentTimeMillis();
+//
+//     Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+//         .setNanos((int) ((millis % 1000) * 1000000)).build();
+//
+//
+// Example 5: Compute Timestamp from current time in Python.
+//
+//     timestamp = Timestamp()
+//     timestamp.GetCurrentTime()
+//
+// # JSON Mapping
+//
+// In JSON format, the Timestamp type is encoded as a string in the
+// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+// where {year} is always expressed using four digits while {month}, {day},
+// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+// is required. A proto3 JSON serializer should always use UTC (as indicated by
+// "Z") when printing the Timestamp type and a proto3 JSON parser should be
+// able to accept both UTC and other timezones (as indicated by an offset).
+//
+// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+// 01:30 UTC on January 15, 2017.
+//
+// In JavaScript, one can convert a Date object to this format using the
+// standard
+// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+// method. In Python, a standard `datetime.datetime` object can be converted
+// to this format using
+// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
+// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
+// the Joda Time's [`ISODateTimeFormat.dateTime()`](
+// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
+// ) to obtain a formatter capable of generating timestamps in this format.
+//
+//
+message Timestamp {
+  // Represents seconds of UTC time since Unix epoch
+  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+  // 9999-12-31T23:59:59Z inclusive.
+  int64 seconds = 1;
+
+  // Non-negative fractions of a second at nanosecond resolution. Negative
+  // second values with fractions must still have non-negative nanos values
+  // that count forward in time. Must be from 0 to 999,999,999
+  // inclusive.
+  int32 nanos = 2;
+}
+
+-- cue.mod --
diff --git a/cue/build/file.go b/cue/build/file.go
index 1ce636a..0e6d09b 100644
--- a/cue/build/file.go
+++ b/cue/build/file.go
@@ -35,7 +35,7 @@
 	YAML     Encoding = "yaml"
 	JSONL    Encoding = "jsonl"
 	Text     Encoding = "text"
-	Protobuf Encoding = "protobuf"
+	Protobuf Encoding = "proto"
 
 	// TODO:
 	// TOML
diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go
index 0217af9..04be952 100644
--- a/internal/encoding/encoder.go
+++ b/internal/encoding/encoder.go
@@ -27,6 +27,7 @@
 	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/format"
 	"cuelang.org/go/cue/token"
+	"cuelang.org/go/encoding/openapi"
 	"cuelang.org/go/internal/filetypes"
 	"cuelang.org/go/pkg/encoding/yaml"
 )
@@ -62,12 +63,12 @@
 
 	switch f.Interpretation {
 	case "":
-	// case build.OpenAPI:
-	// 	// TODO: get encoding options
-	// 	cfg := openapi.Config{}
-	// 	i.interpret = func(inst *cue.Instance) (*ast.File, error) {
-	// 		return openapi.Generate(inst, cfg)
-	// 	}
+	case build.OpenAPI:
+		// TODO: get encoding options
+		cfg := &openapi.Config{}
+		e.interpret = func(i *cue.Instance) (*ast.File, error) {
+			return openapi.Generate(i, cfg)
+		}
 	// case build.JSONSchema:
 	// 	// TODO: get encoding options
 	// 	cfg := openapi.Config{}
@@ -131,7 +132,6 @@
 		e.encFile = func(f *ast.File) error { return format(f.Filename, f) }
 
 	case build.JSON, build.JSONL:
-		// SetEscapeHTML
 		d := json.NewEncoder(w)
 		d.SetIndent("", "    ")
 		d.SetEscapeHTML(cfg.EscapeHTML)
diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go
index f2a936a..f6187e8 100644
--- a/internal/encoding/encoding.go
+++ b/internal/encoding/encoding.go
@@ -12,6 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// TODO: make this package public in cuelang.org/go/encoding
+// once stabalized.
+
 package encoding
 
 import (
@@ -36,25 +39,47 @@
 )
 
 type Decoder struct {
-	cfg      *Config
-	closer   io.Closer
-	next     func() (ast.Expr, error)
-	expr     ast.Expr
-	file     *ast.File
-	filename string // may change on iteration for some formats
-	index    int
-	err      error
+	cfg       *Config
+	closer    io.Closer
+	next      func() (ast.Expr, error)
+	interpret func(*cue.Instance) (file *ast.File, id string, err error)
+	expr      ast.Expr
+	file      *ast.File
+	filename  string // may change on iteration for some formats
+	id        string
+	index     int
+	err       error
 }
 
-func (i *Decoder) Expr() ast.Expr   { return i.expr }
+// ID returns a canonical identifier for the decoded object or "" if no such
+// identifier could be found.
+func (i *Decoder) ID() string {
+	return i.id
+}
+
 func (i *Decoder) Filename() string { return i.filename }
 func (i *Decoder) Index() int       { return i.index }
 func (i *Decoder) Done() bool       { return i.err != nil }
 
 func (i *Decoder) Next() {
-	if i.err == nil {
-		i.expr, i.err = i.next()
-		i.index++
+	if i.err != nil {
+		return
+	}
+	// Decoder level
+	i.expr, i.err = i.next()
+	i.index++
+	if i.err != nil {
+		return
+	}
+	// Interpretations
+	if i.interpret != nil {
+		var r cue.Runtime
+		inst, err := r.CompileFile(i.File())
+		if err != nil {
+			i.err = err
+			return
+		}
+		i.file, i.id, i.err = i.interpret(inst)
 	}
 }
 
@@ -106,6 +131,8 @@
 	Stdin  io.Reader
 	Stdout io.Writer
 
+	PkgName string // package name for files to generate
+
 	Force     bool // overwrite existing files.
 	Stream    bool // will potentially write more than one document per file
 	AllErrors bool
@@ -162,10 +189,13 @@
 		i.err = err
 		i.expr = ast.NewString(string(b))
 	case build.Protobuf:
-		paths := &protobuf.Config{Paths: cfg.ProtoPath}
+		paths := &protobuf.Config{
+			Paths:   cfg.ProtoPath,
+			PkgName: cfg.PkgName,
+		}
 		i.file, i.err = protobuf.Extract(path, r, paths)
 	default:
-		i.err = fmt.Errorf("unsupported stream type %q", f.Encoding)
+		i.err = fmt.Errorf("unsupported encoding %q", f.Encoding)
 	}
 
 	return i