func (l *Loader) Import(path string) (*types.Package, error) {
	if !strings.Contains(path, ".") {
		cfg := &packages.Config{Mode: packages.LoadTypes}
		pkgs, err := packages.Load(cfg, path)
		if err != nil {
			return nil, err
		}
		if len(pkgs) != 1 {
			panic("expected go/packages.Load to return exactly one package")
		}
		return pkgs[0].Types, nil
	}
	pkgs, err := l.Load(path)
	if err != nil {
		return nil, err
	}
	if len(pkgs) != 1 {
		panic("expected Loader.Load to return exactly one package")
	}
	return pkgs[0].Types, nil
}
Import satisfies the go/types.Importer interface.

Unlike standard Go ones like go/importer and x/tools/go/packages, this one is
adapted to load Gunk packages.

Aside from that, it is very similar to standard Go importers that load from
source.
package loader
import (
	"encoding/hex"
	"fmt"
	"go/ast"
	"go/constant"
	"go/parser"
	"go/scanner"
	"go/token"
	"go/types"
	"html/template"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"time"
	"github.com/gunk/gunk/assets"
	"github.com/gunk/gunk/log"
	"golang.org/x/tools/go/packages"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/descriptorpb"
)
type Loader struct {
	Dir  string
	Fset *token.FileSet
	Types bool
	cache map[string]*GunkPackage  
}
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
func hexRand(enclen int) string {
	p := make([]byte, hex.DecodedLen(enclen))
	_, _ = seededRand.Read(p)
	return hex.EncodeToString(p)
}
func (l *Loader) addTempGoFiles() (undo func(), _ error) {
	root := "."
	cmd := exec.Command("go", "list", "-m", "-f={{.Dir}}")
	cmd.Dir = l.Dir
	if out, err := cmd.Output(); err == nil {
		root = strings.TrimSpace(string(out))
	}
	if root == "" {
		return nil, fmt.Errorf("empty module root; missing module?")
	}
	var toDelete []string
	if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			return nil
		}
		if strings.Contains(path, "@v") {
			return filepath.SkipDir
		}
		infos, err := ioutil.ReadDir(path)
		if err != nil {
			return err
		}
		pkgName := info.Name()  
		anyGunk := false
		for _, info := range infos {
			name := info.Name()
			if strings.HasSuffix(name, ".go") {
				return nil
			}
			if strings.HasSuffix(name, ".gunk") {
				f, err := parser.ParseFile(token.NewFileSet(),
					filepath.Join(path, name), nil, parser.PackageClauseOnly)
				if err == nil {
					pkgName = f.Name.Name
				}
				anyGunk = true
				break
			}
		}
		if !anyGunk {
			return nil
		}
		tmpPath := filepath.Join(path, "gunkpkg-"+hexRand(8)+".go")
		if err := ioutil.WriteFile(tmpPath, []byte("package "+pkgName), 0o666); err != nil {
			return err
		}
		toDelete = append(toDelete, tmpPath)
		return nil
	}); err != nil {
		return nil, err
	}
	return func() {
		anyErr := false
		for _, path := range toDelete {
			if err := os.Remove(path); err != nil {
				anyErr = true
				fmt.Fprintf(os.Stderr, "could not delete gunkpkg file: %v", err)
			}
		}
		if anyErr {
			panic("could not delete some of the gunkpkg files")
		}
	}, nil
}
func (l *Loader) Load(patterns ...string) ([]*GunkPackage, error) {
	if len(patterns) == 1 {
		pkgPath := patterns[0]
		if pkg := l.cache[pkgPath]; pkg != nil {
			return []*GunkPackage{pkg}, nil
		}
	}
	var pkgs []*GunkPackage
	loadFiles := len(patterns) > 0 && strings.HasSuffix(patterns[0], ".gunk")
	if loadFiles {
		pkgs = append(pkgs, &GunkPackage{
			Package: packages.Package{
				ID:      "command-line-arguments",
				Name:    "",  
				PkgPath: "command-line-arguments",
			},
			GunkFiles: patterns,
		})
	} else {
		undo, err := l.addTempGoFiles()
		if err != nil {
			return nil, err
		}
		defer undo()
		cfg := &packages.Config{
			Dir:  l.Dir,
			Mode: packages.LoadFiles,
		}
		lpkgs, err := packages.Load(cfg, patterns...)
		if err != nil {
			return nil, err
		}
		for _, lpkg := range lpkgs {
			pkg := &GunkPackage{Package: *lpkg}
			findGunkFiles(pkg)
			if len(pkg.GunkFiles) == 0 {
				continue
			}
			pkgs = append(pkgs, pkg)
		}
	}
	for _, pkg := range pkgs {
		l.parseGunkPackage(pkg)
		l.validatePackage(pkg)
		if l.cache == nil {
			l.cache = make(map[string]*GunkPackage)
		}
		l.cache[pkg.PkgPath] = pkg
	}
	return pkgs, nil
}
func findGunkFiles(pkg *GunkPackage) {
	for _, gofile := range pkg.GoFiles {
		dir := filepath.Dir(gofile)
		if pkg.Dir == "" {
			pkg.Dir = dir
		} else if dir != pkg.Dir {
			pkg.addError(ListError, 0, nil, "multiple dirs for %s: %s %s",
				pkg.PkgPath, pkg.Dir, dir)
			return  
		}
	}
	matches, err := filepath.Glob(filepath.Join(pkg.Dir, "*.gunk"))
	if err != nil {
		panic(err.Error())
	}
	pkg.GunkFiles = matches
}
const (
	UnknownError = packages.UnknownError
	ListError    = packages.ListError
	ParseError   = packages.ParseError
	TypeError    = packages.TypeError
	ValidateError = packages.TypeError + 10 + iota
)