func ExportAsJSON(index *rpc.Index) error {
	f, err := os.Create("index.json")
	if err != nil {
		return err
	}
	defer f.Close()
	w := bufio.NewWriter(f)
	m := &jsonpb.Marshaler{
		Indent: "  ",
	}
	err = m.Marshal(w, index)
	w.Flush()
	return err
}
ExportAsJSON writes a index as a JSON file
package core
import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"github.com/apigee/registry/rpc"
	"github.com/gogo/protobuf/jsonpb"
	"github.com/yoheimuta/go-protoparser/v4/interpret/unordered"
	"github.com/yoheimuta/go-protoparser/v4/parser"
	protoparser "github.com/yoheimuta/go-protoparser/v4"
)
func NewIndexFromZippedProtos(b []byte) (*rpc.Index, error) {
	dname, err := ioutil.TempDir("", "registry-protos-")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(dname)
	_, err = UnzipArchiveToPath(b, dname)
	if err != nil {
		return nil, err
	}
	c, err := indexForRoot(dname)
	if err != nil {
		return nil, err
	}
	buildIndex(c)
	removeRequestAndResponseSchemas(c)
	flattenPaths(c)
	return c, nil
}
func indexForRoot(root string) (*rpc.Index, error) {
	s := &rpc.Index{}
	err := filepath.Walk(root,
		func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if strings.HasSuffix(path, ".proto") {
				file, err := fileForProto(path, root+"/")
				if err != nil {
					return err
				}
				s.Files = append(s.Files, file)
			}
			return nil
		})
	return s, err
}
func fileForProto(filename, root string) (*rpc.File, error) {
	reader, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	p, err := protoparser.Parse(
		reader,
		protoparser.WithDebug(false),
		protoparser.WithPermissive(true),
		protoparser.WithFilename(filepath.Base(filename)),
	)
	if err != nil {
		return nil, err
	}
	v, err := protoparser.UnorderedInterpret(p)
	if err != nil {
		return nil, err
	}
	f := &rpc.File{}
	f.Name = strings.TrimPrefix(filename, root)
	for _, m := range v.ProtoBody.Messages {
		f.Schemas = append(f.Schemas, schemaForMessage(m))
	}
	for _, s := range v.ProtoBody.Services {
		serviceName := s.ServiceName
		for _, rpc := range s.ServiceBody.RPCs {
			f.Operations = append(f.Operations, operationForRPC(rpc, serviceName))
		}
	}
	return f, nil
}
func schemaForMessage(m *unordered.Message) *rpc.Schema {
	s := &rpc.Schema{}
	s.Name = m.MessageName
	for _, f := range m.MessageBody.Fields {
		field := &rpc.Field{}
		field.Name = f.FieldName
		s.Fields = append(s.Fields, field)
	}
	for _, opt := range m.MessageBody.Options {
		processOptionForSchema(s, opt)
	}
	return s
}
func linesForOption(opt *parser.Option) []string {
	body := opt.Constant
	body = strings.TrimPrefix(body, "{")
	body = strings.TrimSuffix(body, "}")
	body = strings.ReplaceAll(body, ",", "\n")
	return strings.Split(body, "\n")
}
func processOptionForSchema(s *rpc.Schema, opt *parser.Option) {
	if opt.OptionName == "(google.api.resource)" {
		lines := linesForOption(opt)
		for _, line := range lines {
			pair := strings.SplitN(line, ":", 2)
			if len(pair) == 2 {
				name := pair[0]
				if name == "type" {
					s.Type = trimQuotes(pair[1])
				}
				if name == "pattern" {
					s.Name = trimQuotes(pair[1])
				}
			}
		}
	}
}
func operationForRPC(myrpc *parser.RPC, serviceName string) *rpc.Operation {
	op := &rpc.Operation{}
	op.Name = myrpc.RPCName
	op.Service = serviceName
	for _, opt := range myrpc.Options {
		processOptionForOperation(op, opt)
	}
	return op
}
func processOptionForOperation(op *rpc.Operation, opt *parser.Option) {
	if opt.OptionName == "(google.api.http)" {
		lines := linesForOption(opt)
		for _, line := range lines {
			pair := strings.SplitN(line, ":", 2)
			if len(pair) == 2 {
				name := pair[0]
				if name == "get" || name == "post" || name == "put" || name == "delete" || name == "patch" {
					op.Verb = name
					op.Path = trimQuotes(pair[1])
				}
			}
		}
	}
}
func trimQuotes(s string) string {
	s = strings.TrimPrefix(s, "\"")
	s = strings.TrimSuffix(s, "\"")
	return s
}
func flattenPaths(index *rpc.Index) {
	r1 := regexp.MustCompile("{[^{}=]+=([^{}=]+)}")
	r2 := regexp.MustCompile("{[^{}].*}")
	for _, op := range index.Operations {
		p := op.Path
		p = strings.ReplaceAll(p, "{$api_version}", "v*")
		p = r1.ReplaceAllString(p, "$1")
		p = r2.ReplaceAllString(p, "*")
		op.Path = p
	}
}
func buildIndex(index *rpc.Index) {
	index.Fields = make([]*rpc.Field, 0)
	index.Schemas = make([]*rpc.Schema, 0)
	index.Operations = make([]*rpc.Operation, 0)
	for _, file := range index.Files {
		for _, op := range file.Operations {
			index.Operations = append(index.Operations, &rpc.Operation{
				Name:    op.GetName(),
				Service: op.GetService(),
				Verb:    op.GetVerb(),
				Path:    op.GetPath(),
				File:    file.GetName(),
			})
		}
		for _, schema := range file.Schemas {
			index.Schemas = append(index.Schemas, &rpc.Schema{
				Name:     schema.GetName(),
				Resource: schema.GetResource(),
				Type:     schema.GetType(),
				File:     file.GetName(),
				Fields:   nil,
			})
			for _, field := range schema.Fields {
				index.Fields = append(index.Fields, &rpc.Field{
					Name:   field.GetName(),
					Schema: schema.GetName(),
					File:   file.GetName(),
				})
			}
		}
	}
	sort.Slice(index.Fields, func(i, j int) bool {
		return index.Fields[i].Name < index.Fields[j].Name
	})
	sort.Slice(index.Schemas, func(i, j int) bool {
		return index.Schemas[i].Name < index.Schemas[j].Name
	})
	sort.Slice(index.Operations, func(i, j int) bool {
		return index.Operations[i].Name < index.Operations[j].Name
	})
}
func removeRequestAndResponseSchemas(index *rpc.Index) {
	filteredSchemas := make([]*rpc.Schema, 0)
	for _, schema := range index.Schemas {
		if strings.HasSuffix(schema.Name, "Request") ||
			strings.HasSuffix(schema.Name, "Response") {
		} else {
			filteredSchemas = append(filteredSchemas, schema)
		}
	}
	index.Schemas = filteredSchemas
}
func ExportSchemas(index *rpc.Index) error {
	f, err := os.Create("schemas.csv")
	if err != nil {
		return err
	}
	defer f.Close()
	w := bufio.NewWriter(f)
	for _, schema := range index.Schemas {
		fmt.Fprintf(w, "%s,%s,%s,%s\n",
			schema.Name, schema.Resource, schema.Type, schema.File)
	}
	w.Flush()
	return nil
}
func ExportOperations(index *rpc.Index) error {
	f, err := os.Create("operations.csv")
	if err != nil {
		return err
	}
	defer f.Close()
	w := bufio.NewWriter(f)
	for _, op := range index.Operations {
		fmt.Fprintf(w, "%s,%s,%s,%s,%s\n",
			op.Name, op.Service, op.Verb, op.Path, op.File)
	}
	w.Flush()
	return nil
}
func ExportFields(index *rpc.Index) error {
	f, err := os.Create("fields.csv")
	if err != nil {
		return err
	}
	defer f.Close()
	w := bufio.NewWriter(f)
	for _, field := range index.Fields {
		fmt.Fprintf(w, "%s,%s,%s\n",
			field.Name, field.Schema, field.File)
	}
	w.Flush()
	return nil
}