Full Code of nitrous-io/goop for AI

master 78b83e479865 cached
17 files
29.5 KB
9.6k tokens
50 symbols
1 requests
Download .txt
Repository: nitrous-io/goop
Branch: master
Commit: 78b83e479865
Files: 17
Total size: 29.5 KB

Directory structure:
gitextract__nt8qh5a/

├── .gitignore
├── Goopfile
├── LICENSE
├── Makefile
├── README.md
├── colors/
│   └── colors.go
├── goop/
│   ├── goget.go
│   ├── goop.go
│   ├── goop_test.go
│   ├── vcs.go
│   └── vcs_test.go
├── main.go
├── parser/
│   ├── dependency.go
│   ├── parser.go
│   └── parser_test.go
└── pkg/
    └── env/
        ├── env.go
        └── env_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.vendor

# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
*.test

# Folders
_obj/
_test/
bin/
_vendor/
build/

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe

*.swp
**.orig
.env

# OSX
.DS_Store
._*
.Spotlight-V100
.Trashes

# Linux
*~
.directory

# Windows
Thumbs.db
Desktop.ini

# RubyMine
.idea/

# TextMate
*.tmproj
*.tmproject
tmtags

# Vim
.*.sw[a-z]
*.un~
Session.vim
tags

# Emacs
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
.elc
auto-save-list
tramp
.\#*

# Vagrant
.vagrant


================================================
FILE: Goopfile
================================================
github.com/onsi/ginkgo/ginkgo
github.com/onsi/gomega
code.google.com/p/go.tools/go/vcs


================================================
FILE: LICENSE
================================================
Copyright (c) 2014 Irrational Industries, Inc. d.b.a. Nitrous.IO.

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.


================================================
FILE: Makefile
================================================
NO_COLOR=\033[0m
OK_COLOR=\033[32;01m
ERROR_COLOR=\033[31;01m
WARN_COLOR=\033[33;01m
BIN_NAME=goop

all: build

build:
	@mkdir -p build/
	@echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
	go get -d -v ./...
	@echo "$(OK_COLOR)==> Building$(NO_COLOR)"
	go install -x ./...
	cp $(GOPATH)/bin/$(BIN_NAME) build/$(BIN_NAME)

format:
	go fmt ./...

test:
	@echo "$(OK_COLOR)==> Testing...$(NO_COLOR)"
	@go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs -n1 go get -d
	@goop exec ginkgo -r -trace -keepGoing

.PHONY: all build format test


================================================
FILE: README.md
================================================
Goop
====

![Goopie](https://raw.githubusercontent.com/nitrous-io/goop/master/goopie.png)

A dependency manager for Go (golang), inspired by Bundler. It is different from other dependency managers in that it does not force you to mess with your `GOPATH`.

### Getting Started

1. Install Goop: `go get github.com/nitrous-io/goop`

2. Create `Goopfile`. Revision reference (e.g. Git SHA hash) is optional, but recommended. Prefix hash with `#`. (This is to futureproof the file format.)

   Example:
   ```
   github.com/mattn/go-sqlite3
   github.com/gorilla/context #14f550f51af52180c2eefed15e5fd18d63c0a64a
   github.com/dotcloud/docker/pkg/proxy #v1.0.1 // comment
   github.com/gorilla/mux !git@github.com:nitrous-io/mux.git // override repo url
   ```

3. Run `goop install`. This will install packages inside a subdirectory called `.vendor` and create `Goopfile.lock`, recording exact versions used for each package and its dependencies. Subsequent `goop install` runs will ignore `Goopfile` and install the versions specified in `Goopfile.lock`. You should check this file in to your source version control. It's a good idea to add `.vendor` to your version control system's ignore settings (e.g. `.gitignore`).

4. Run commands using `goop exec` (e.g. `goop exec make`). This will execute your command in an environment that has correct `GOPATH` and `PATH` set.

5. Go commands can be run without the `exec` keyword (e.g. `goop go test`).

### Other commands

* Run `goop update` to ignore an existing `Goopfile.lock`, and update to latest versions of packages (as specified in `Goopfile`).

* Running `eval $(goop env)` will modify `GOPATH` and `PATH` in current shell session, allowing you to run commands without `goop exec`.

### Caveat

Goop currently only supports Git and Mercurial. This should be fine for 99% of the cases, but you are more than welcome to make a pull request that adds support for Subversion and Bazaar.

- - -
[Work on awesome golang projects, like Goop, at Nitrous.IO](http://www.nitrous.io/jobs/?utm_source=nitrous.io&utm_medium=goop_readme&utm_campaign=goop_readme)

Copyright (c) 2014 Irrational Industries, Inc. d.b.a. Nitrous.IO.<br>
This software is licensed under the [MIT License](http://github.com/nitrous-io/goop/raw/master/LICENSE).


================================================
FILE: colors/colors.go
================================================
package colors

const (
	Reset = "\033[0m"
	OK    = "\033[0;32m"
	Error = "\033[0;31m"
	Warn  = "\033[0;33m"
)


================================================
FILE: goop/goget.go
================================================
package goop

import (
	"io"
	"os/exec"
	"regexp"
)

var goGetDownloadRe = regexp.MustCompile(`(?m)^(\S+)\s+\(download\)$`)

type DownloadRecorder struct {
	downloads map[string]struct{}
	writer    io.Writer
}

func NewDownloadRecorder(writer io.Writer) *DownloadRecorder {
	return &DownloadRecorder{downloads: map[string]struct{}{}, writer: writer}
}

func (d *DownloadRecorder) Write(p []byte) (n int, err error) {
	s := string(p)
	matches := goGetDownloadRe.FindAllStringSubmatch(s, -1)
	if matches != nil {
		for _, m := range matches {
			d.downloads[m[1]] = struct{}{}
		}
	}
	return d.writer.Write(p)
}

func (d *DownloadRecorder) Downloads() []string {
	s := make([]string, 0, len(d.downloads))
	for k, _ := range d.downloads {
		s = append(s, k)
	}
	return s
}

func (g *Goop) goGet(pkgpath string, gopath string) ([]string, error) {
	cmd := exec.Command("go", "get", "-d", "-v", "./...")
	env := g.patchedEnv(true)
	env["GOPATH"] = gopath
	cmd.Dir = pkgpath
	cmd.Env = env.Strings()
	cmd.Stdin = g.stdin
	cmd.Stdout = g.stdout
	dlRec := NewDownloadRecorder(g.stderr)
	cmd.Stderr = dlRec
	err := cmd.Run()
	if err != nil {
		return nil, err
	}

	return dlRec.Downloads(), nil
}


================================================
FILE: goop/goop.go
================================================
package goop

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path"
	"sort"
	"strings"

	"code.google.com/p/go.tools/go/vcs"

	"github.com/nitrous-io/goop/colors"
	"github.com/nitrous-io/goop/parser"
	"github.com/nitrous-io/goop/pkg/env"
)

type UnsupportedVCSError struct {
	VCS string
}

func (e *UnsupportedVCSError) Error() string {
	return fmt.Sprintf("%s is not supported.", e.VCS)
}

type Goop struct {
	dir    string
	stdin  io.Reader
	stdout io.Writer
	stderr io.Writer
}

func NewGoop(dir string, stdin io.Reader, stdout io.Writer, stderr io.Writer) *Goop {
	return &Goop{dir: dir, stdin: stdin, stdout: stdout, stderr: stderr}
}

func (g *Goop) patchedEnv(replaceGopath bool) env.Env {
	e := env.NewEnv()

	binPath := path.Join(g.vendorDir(), "bin")

	if replaceGopath {
		e["GOPATH"] = g.vendorDir()
	} else {
		e.Prepend("GOPATH", g.vendorDir())
	}
	e["GOBIN"] = binPath
	e.Prepend("PATH", binPath)

	return e
}

func (g *Goop) PrintEnv() {
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		g.stdout.Write([]byte(fmt.Sprintf("GOPATH=%s\n", g.vendorDir())))
	} else {
		g.stdout.Write([]byte(fmt.Sprintf("GOPATH=%s:%s\n", g.vendorDir(), gopath)))
	}
	g.stdout.Write([]byte(fmt.Sprintf("PATH=%s:%s\n", path.Join(g.vendorDir(), "bin"), os.Getenv("PATH"))))
}

func (g *Goop) Exec(name string, args ...string) error {
	vname := path.Join(g.vendorDir(), "bin", name)
	_, err := os.Stat(vname)
	if err == nil {
		name = vname
	}
	cmd := exec.Command(name, args...)
	cmd.Env = g.patchedEnv(false).Strings()
	cmd.Stdin = g.stdin
	cmd.Stdout = g.stdout
	cmd.Stderr = g.stderr
	return cmd.Run()
}

func (g *Goop) Install() error {
	writeLockFile := false
	f, err := os.Open(path.Join(g.dir, "Goopfile.lock"))
	if err == nil {
		g.stdout.Write([]byte(colors.OK + "Using Goopfile.lock..." + colors.Reset + "\n"))
	} else {
		f, err = os.Open(path.Join(g.dir, "Goopfile"))
		if err != nil {
			return err
		}
		writeLockFile = true
	}
	return g.parseAndInstall(f, writeLockFile)
}

func (g *Goop) Update() error {
	f, err := os.Open(path.Join(g.dir, "Goopfile"))
	if err != nil {
		return err
	}
	return g.parseAndInstall(f, true)
}

func (g *Goop) parseAndInstall(goopfile *os.File, writeLockFile bool) error {
	defer goopfile.Close()

	deps, err := parser.Parse(goopfile)
	if err != nil {
		return err
	}

	srcPath := path.Join(g.vendorDir(), "src")
	tmpGoPath := path.Join(g.vendorDir(), "tmp")
	tmpSrcPath := path.Join(tmpGoPath, "src")

	err = os.RemoveAll(tmpGoPath)
	if err != nil {
		return err
	}
	err = os.MkdirAll(tmpSrcPath, 0775)
	if err != nil {
		return err
	}

	repos := map[string]*vcs.RepoRoot{}
	lockedDeps := map[string]*parser.Dependency{}

	for _, dep := range deps {
		if dep.URL == "" {
			g.stdout.Write([]byte(colors.OK + "=> Fetching " + dep.Pkg + "..." + colors.Reset + "\n"))
		} else {
			g.stdout.Write([]byte(colors.OK + "=> Fetching " + dep.Pkg + " from " + dep.URL + "..." + colors.Reset + "\n"))
		}

		repo, err := repoForDep(dep)
		if err != nil {
			return err
		}
		repos[dep.Pkg] = repo

		pkgPath := path.Join(srcPath, repo.Root)
		tmpPkgPath := path.Join(tmpSrcPath, repo.Root)

		err = os.MkdirAll(path.Join(tmpPkgPath, ".."), 0775)
		if err != nil {
			return err
		}

		noclone := false

		exists, err := pathExists(pkgPath)
		if err != nil {
			return err
		}
		tmpExists, err := pathExists(tmpPkgPath)
		if err != nil {
			return err
		}
		if exists {
			// if package already exists, just symlink package dir and skip cloning
			g.stderr.Write([]byte(colors.Warn + "Warning: " + pkgPath + " already exists; skipping!" + colors.Reset + "\n"))
			if !tmpExists {
				err = os.Symlink(pkgPath, tmpPkgPath)
				if err != nil {
					return err
				}
			}
			noclone = true
		} else {
			noclone = tmpExists
		}

		if !noclone {
			// clone repo
			err = g.clone(repo.VCS.Cmd, repo.Repo, tmpPkgPath)
			if err != nil {
				return err
			}
		}

		// if rev is not given, record current rev in path
		if dep.Rev == "" {
			rev, err := g.currentRev(repo.VCS.Cmd, tmpPkgPath)
			if err != nil {
				return err
			}
			dep.Rev = rev
		}
		lockedDeps[dep.Pkg] = dep

		// checkout specified rev
		err = g.checkout(repo.VCS.Cmd, tmpPkgPath, dep.Rev)
		if err != nil {
			return err
		}
	}

	for _, dep := range deps {
		g.stdout.Write([]byte(colors.OK + "=> Fetching dependencies for " + dep.Pkg + "..." + colors.Reset + "\n"))

		repo := repos[dep.Pkg]
		tmpPkgPath := path.Join(tmpSrcPath, repo.Root)

		// fetch sub-dependencies
		subdeps, err := g.goGet(tmpPkgPath, tmpGoPath)
		if err != nil {
			return err
		}

		for _, subdep := range subdeps {
			subdepRepo, err := vcs.RepoRootForImportPath(subdep, true)
			if err != nil {
				return err
			}

			subdepPkgPath := path.Join(tmpSrcPath, subdepRepo.Root)

			rev, err := g.currentRev(subdepRepo.VCS.Cmd, subdepPkgPath)
			if err != nil {
				return err
			}

			err = g.checkout(subdepRepo.VCS.Cmd, subdepPkgPath, rev)
			if err != nil {
				return err
			}

			repos[subdep] = subdepRepo
			lockedDeps[subdep] = &parser.Dependency{Pkg: subdep, Rev: rev}
		}
	}

	for _, dep := range lockedDeps {
		g.stdout.Write([]byte(colors.OK + "=> Installing " + dep.Pkg + "..." + colors.Reset + "\n"))

		repo := repos[dep.Pkg]
		pkgPath := path.Join(srcPath, repo.Root)
		tmpPkgPath := path.Join(tmpSrcPath, repo.Root)

		err = os.MkdirAll(path.Join(pkgPath, ".."), 0775)
		if err != nil {
			return err
		}

		lfi, err := os.Lstat(tmpPkgPath)
		if err != nil && !os.IsNotExist(err) {
			return err
		}
		if err == nil {
			if lfi.Mode()&os.ModeSymlink == 0 {
				// move package to vendor path
				err = os.RemoveAll(pkgPath)
				if err != nil {
					return err
				}
				err = os.Rename(tmpPkgPath, pkgPath)
			} else {
				// package already in vendor path, just remove the symlink
				err = os.Remove(tmpPkgPath)
			}
			if err != nil {
				return err
			}
		}
	}

	for _, dep := range lockedDeps {
		// install
		repo := repos[dep.Pkg]
		pkgPath := path.Join(srcPath, repo.Root)
		cmd := g.command(pkgPath, "go", "install", "-x", dep.Pkg)
		cmd.Env = g.patchedEnv(true).Strings()
		cmd.Run()
	}

	err = os.RemoveAll(tmpGoPath)
	if err != nil {
		return err
	}

	// in order to minimize diffs, we sort lockedDeps first and write the
	// sorted results
	if writeLockFile {
		lf, err := os.Create(path.Join(g.dir, "Goopfile.lock"))
		defer lf.Close()

		var keys []string
		for k := range lockedDeps {
			keys = append(keys, k)
		}
		sort.Strings(keys)

		for _, k := range keys {
			dep := lockedDeps[k]
			_, err = lf.WriteString(dep.String() + "\n")
			if err != nil {
				return err
			}
		}
	}

	g.stdout.Write([]byte(colors.OK + "=> Done!" + colors.Reset + "\n"))

	return nil
}

func (g *Goop) vendorDir() string {
	return path.Join(g.dir, ".vendor")
}

func (g *Goop) currentRev(vcsCmd string, path string) (string, error) {
	switch vcsCmd {
	case "git":
		cmd := exec.Command("git", "rev-parse", "--verify", "HEAD")
		cmd.Dir = path
		cmd.Stderr = g.stderr
		rev, err := cmd.Output()
		if err != nil {
			return "", err
		} else {
			return strings.TrimSpace(string(rev)), err
		}
	case "hg":
		cmd := exec.Command("hg", "log", "-r", ".", "--template", "{node}")
		cmd.Dir = path
		cmd.Stderr = g.stderr
		rev, err := cmd.Output()
		if err != nil {
			return "", err
		} else {
			return strings.TrimSpace(string(rev)), err
		}
	}
	return "", &UnsupportedVCSError{VCS: vcsCmd}
}

func (g *Goop) clone(vcsCmd string, url string, clonePath string) error {
	switch vcsCmd {
	case "git":
		return g.command("", "git", "clone", url, clonePath).Run()
	case "hg":
		return g.command("", "hg", "clone", url, clonePath).Run()
	}
	return &UnsupportedVCSError{VCS: vcsCmd}
}

func (g *Goop) checkout(vcsCmd string, path string, tag string) error {
	g.stdout.Write([]byte("Checking out \"" + tag + "\"\n"))
	switch vcsCmd {
	case "git":
		err := g.command(path, "git", "fetch").Run()
		if err != nil {
			return err
		}
		return g.quietCommand(path, "git", "checkout", tag).Run()
	case "hg":
		err := g.command(path, "hg", "pull").Run()
		if err != nil {
			return err
		}
		return g.quietCommand(path, "hg", "update", tag).Run()
	}
	return &UnsupportedVCSError{VCS: vcsCmd}
}

func (g *Goop) command(path string, name string, args ...string) *exec.Cmd {
	cmd := exec.Command(name, args...)
	cmd.Dir = path
	cmd.Stdin = g.stdin
	cmd.Stdout = g.stdout
	cmd.Stderr = g.stderr
	return cmd
}

func (g *Goop) quietCommand(path string, name string, args ...string) *exec.Cmd {
	cmd := g.command(path, name, args...)
	cmd.Stdout = nil
	cmd.Stderr = nil
	return cmd
}

func repoForDep(dep *parser.Dependency) (*vcs.RepoRoot, error) {
	if dep.URL != "" {
		return RepoRootForImportPathWithURLOverride(dep.Pkg, dep.URL)
	}
	return vcs.RepoRootForImportPath(dep.Pkg, true)
}

// pathExists returns:
// * (true, nil) if path exists
// * (false, nil) if path does not exist
// * (false, err) if error happened during stat
func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	switch {
	case err != nil && !os.IsNotExist(err): // unexpected err
		return false, err
	case err != nil && os.IsNotExist(err):
		return false, nil
	case err == nil:
		return true, nil
	default:
		panic("never reached")
	}
}


================================================
FILE: goop/goop_test.go
================================================
package goop_test

import (
	"testing"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

func Test(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "goop")
}


================================================
FILE: goop/vcs.go
================================================
package goop

import (
	"os/exec"
	"strings"

	"code.google.com/p/go.tools/go/vcs"
)

func GuessVCS(url string) string {
	switch {
	case strings.HasPrefix(url, "https://github.com"):
		return "git"
	case strings.HasPrefix(url, "git://"):
		return "git"
	case strings.HasPrefix(url, "git+ssh://"):
		return "git"
	case strings.HasPrefix(url, "git@"):
		return "git"
	case strings.HasPrefix(url, "ssh://hg@"):
		return "hg"
	default:
		return ""
	}
}

func IdentifyVCS(url string) string {
	v := map[string][]string{
		"git": []string{"git", "ls-remote"},
		"hg":  []string{"hg", "identify"},
	}
	tryVCS := func(vcs string) bool {
		cmd := v[vcs]
		delete(v, vcs)
		return exec.Command(cmd[0], append(cmd[1:], url)...).Run() == nil // use vcs.VCS.Ping?
	}
	guess := GuessVCS(url)
	if guess != "" && v[guess] != nil {
		if tryVCS(guess) {
			return guess
		}
	}
	for k, _ := range v {
		if tryVCS(k) {
			return k
		}
	}
	return ""
}

func RepoRootForImportPathWithURLOverride(importPath string, url string) (*vcs.RepoRoot, error) {
	repo, err := vcs.RepoRootForImportPathStatic(importPath, "ignore")
	if err != nil {
		return nil, err
	}
	repo.Repo = url
	return repo, nil
}


================================================
FILE: goop/vcs_test.go
================================================
package goop_test

import (
	"github.com/nitrous-io/goop/goop"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("vcs", func() {
	tests := []struct {
		importPath string
		url        string
		guess      string
		actual     string
		repoRoot   string
	}{
		{"github.com/dotcloud/docker/pkg/term", "git://github.com/dotcloud/docker.git", "git", "git", "github.com/dotcloud/docker"},
		{"github.com/mattn/go-sqlite3", "git://github.com/mattn/go-sqlite3.git", "git", "git", "github.com/mattn/go-sqlite3"},
		{"github.com/mattn/go-sqlite3", "https://github.com/mattn/go-sqlite3.git", "git", "git", "github.com/mattn/go-sqlite3"},
		{"github.com/mattn/go-sqlite3", "git+ssh://git@github.com/mattn/go-sqlite3.git", "git", "git", "github.com/mattn/go-sqlite3"},
		{"github.com/mattn/go-sqlite3", "git@github.com:mattn/go-sqlite3.git", "git", "git", "github.com/mattn/go-sqlite3"},
		{"github.com/nitrous-io/no-such", "git@github.com/nitrous-io/no-such", "git", "", "github.com/nitrous-io/no-such"},
		{"bitbucket.org/kardianos/osext", "ssh://hg@bitbucket.org/kardianos/osext", "hg", "hg", "bitbucket.org/kardianos/osext"},
		{"bitbucket.org/kardianos/osext", "https://bitbucket.org/kardianos/osext", "", "hg", "bitbucket.org/kardianos/osext"},
		{"bitbucket.org/ymotongpoo/go-bitarray", "git@bitbucket.org:ymotongpoo/go-bitarray.git", "git", "git", "bitbucket.org/ymotongpoo/go-bitarray"},
		{"bitbucket.org/ymotongpoo/go-bitarray", "https://bitbucket.org/ymotongpoo/go-bitarray.git", "", "git", "bitbucket.org/ymotongpoo/go-bitarray"},
		{"code.google.com/p/go.tools/go/vcs", "https://code.google.com/p/go.tools/", "", "hg", "code.google.com/p/go.tools"},
		// not supported yet - {"example.com/foo/go-sqlite3", "git@github.com:mattn/go-sqlite3.git", "git", "git", "example.com/foo/go-sqlite3"},
	}

	Describe("GuessVCS()", func() {
		for _, test := range tests {
			t := test
			Context(t.url, func() {
				It("returns "+t.guess, func() {
					Expect(goop.GuessVCS(t.url)).To(Equal(t.guess))
				})
			})
		}
	})

	// slow test: this test requires network connection
	XDescribe("IdentifyVCS()", func() {
		for _, test := range tests {
			t := test
			Context(t.url, func() {
				It("returns "+t.actual, func() {
					Expect(goop.IdentifyVCS(t.url)).To(Equal(t.actual))
				})
			})
		}
	})

	Describe("RepoRootForImportPathWithURLOverride()", func() {
		for _, test := range tests {
			t := test
			Context(t.url, func() {
				It("returns "+t.repoRoot, func() {
					repo, err := goop.RepoRootForImportPathWithURLOverride(t.importPath, t.url)
					Expect(err).To(BeNil())
					Expect(repo).NotTo(BeNil())
					Expect(repo.Repo).To(Equal(t.url))
					Expect(repo.Root).To(Equal(t.repoRoot))
				})
			})
		}
	})
})


================================================
FILE: main.go
================================================
package main

import (
	"errors"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/nitrous-io/goop/colors"
	"github.com/nitrous-io/goop/goop"
)

func main() {
	name := path.Base(os.Args[0])

	pwd, err := os.Getwd()
	if err != nil {
		os.Stderr.WriteString(colors.Error + name + ": failed to determine present working directory!" + colors.Reset + "\n")
	}

	g := goop.NewGoop(path.Join(pwd), os.Stdin, os.Stdout, os.Stderr)

	if len(os.Args) < 2 {
		printUsage()
	}

	cmd := os.Args[1]
	switch cmd {
	case "help":
		printUsage()
	case "install":
		err = g.Install()
	case "update":
		err = g.Update()
	case "exec":
		if len(os.Args) < 3 {
			printUsage()
		}
		err = g.Exec(os.Args[2], os.Args[3:]...)
	case "go":
		if len(os.Args) < 3 {
			printUsage()
		}
		err = g.Exec("go", os.Args[2:]...)
	case "env":
		g.PrintEnv()
	default:
		err = errors.New(`unrecognized command "` + cmd + `"`)
	}

	if err != nil {
		errMsg := err.Error()
		code := 1

		// go does not provide a cross-platform way to get exit status, so inspect error message instead
		// https://code.google.com/p/go/source/browse/src/pkg/os/exec_posix.go#119
		if strings.HasPrefix(errMsg, "exit status ") {
			code, err = strconv.Atoi(errMsg[len("exit status "):])
			if err != nil {
				code = 1
			}
			errMsg = "Command failed with " + errMsg
		}

		os.Stderr.WriteString(colors.Error + name + ": " + errMsg + colors.Reset + "\n")
		os.Exit(code)
	}
}

func printUsage() {
	os.Stdout.WriteString(strings.TrimSpace(usage) + "\n\n")
	os.Exit(0)
}

const usage = `
Goop is a tool for managing Go dependencies.

        goop command [arguments]

The commands are:

    install     install the dependencies specified by Goopfile or Goopfile.lock
    update      update dependencies to their latest versions
    env         print GOPATH and PATH environment variables, with the vendor path prepended
    exec        execute a command in the context of the installed dependencies
    go          execute a go command in the context of the installed dependencies
    help        print this message
`


================================================
FILE: parser/dependency.go
================================================
package parser

import "strings"

type Dependency struct {
	Pkg string
	Rev string
	URL string
}

func (d *Dependency) String() string {
	s := make([]string, 0, 3)
	s = append(s, d.Pkg)
	if d.Rev != "" {
		s = append(s, "#"+d.Rev)
	}
	if d.URL != "" {
		s = append(s, "!"+d.URL)
	}
	return strings.Join(s, " ")
}


================================================
FILE: parser/parser.go
================================================
package parser

import (
	"bufio"
	"fmt"
	"io"
	"strings"
)

type ParseError struct {
	LineNum  uint
	LineText string
	Message  string
}

const (
	CommentOrPackage = iota
	URLOr
)

const (
	TokenComment = "//"
	TokenRev     = "#"
	TokenURL     = "!"
)

func (e *ParseError) Error() string {
	return fmt.Sprintf("Parse failed at line %d - %s\n  %s", e.LineNum, e.LineText, e.Message)
}

func Parse(r io.Reader) ([]*Dependency, error) {
	s := bufio.NewScanner(r)
	ln := uint(0)
	deps := []*Dependency{}

	for s.Scan() {
		ln++
		line := strings.TrimSpace(s.Text())
		tokens := strings.Fields(line)

		if line == "" || strings.HasPrefix(tokens[0], TokenComment) {
			continue
		}

		dep := &Dependency{Pkg: tokens[0]}
		parseErr := &ParseError{LineNum: ln, LineText: line}

		for _, t := range tokens[1:] {
			if strings.HasPrefix(t, TokenComment) {
				break
			}
			switch {
			case strings.HasPrefix(t, TokenRev):
				if dep.Rev != "" {
					parseErr.Message = "Multiple revisions given"
					return nil, parseErr
				}
				dep.Rev = t[1:]
			case strings.HasPrefix(t, TokenURL):
				if dep.URL != "" {
					parseErr.Message = "Multiple URLs given"
					return nil, parseErr
				}
				dep.URL = t[1:]
			default:
				parseErr.Message = "Unrecognized token given"
				return nil, parseErr
			}
		}
		deps = append(deps, dep)
	}

	if err := s.Err(); err != nil {
		return nil, err
	}
	return deps, nil
}


================================================
FILE: parser/parser_test.go
================================================
package parser_test

import (
	"bytes"
	"testing"

	"github.com/nitrous-io/goop/parser"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

func Test(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "parser")
}

var _ = Describe("parser", func() {
	Describe("Parse()", func() {
		var (
			deps []*parser.Dependency
			err  error
		)

		Context("empty Goopfile", func() {
			BeforeEach(func() {
				deps, err = parser.Parse(bytes.NewBufferString(""))
			})

			It("returns an empty slice", func() {
				Expect(err).To(BeNil())
				Expect(deps).NotTo(BeNil())
				Expect(deps).To(HaveLen(0))
			})
		})

		Context("one entry", func() {
			Context("with no revision specified", func() {
				BeforeEach(func() {
					deps, err = parser.Parse(bytes.NewBufferString(`
						github.com/nitrous-io/goop
					`))
				})

				It("parses and returns a slice containing one dependency item", func() {
					Expect(err).To(BeNil())
					Expect(deps).To(HaveLen(1))
					Expect(deps[0]).To(Equal(&parser.Dependency{Pkg: "github.com/nitrous-io/goop", Rev: ""}))
				})
			})

			Context("with revision specified", func() {
				It("parses and returns a slice containing one dependency item", func() {
					deps, err = parser.Parse(bytes.NewBufferString(`
						github.com/nitrous-io/goop #09f0feb1b103933bd9985f0a85e01eeaad8d75c8
					`))
					Expect(err).To(BeNil())
					Expect(deps).To(HaveLen(1))
					Expect(deps[0]).To(Equal(&parser.Dependency{
						Pkg: "github.com/nitrous-io/goop",
						Rev: "09f0feb1b103933bd9985f0a85e01eeaad8d75c8",
					}))
				})

				It("ignores whitespace", func() {
					deps, err = parser.Parse(bytes.NewBufferString(`
						github.com/nitrous-io/goop      #09f0feb1b103933bd9985f0a85e01eeaad8d75c8
					`))
					Expect(err).To(BeNil())
					Expect(deps).To(HaveLen(1))
					Expect(deps[0]).To(Equal(&parser.Dependency{
						Pkg: "github.com/nitrous-io/goop",
						Rev: "09f0feb1b103933bd9985f0a85e01eeaad8d75c8",
					}))
				})
			})

			Context("with custom repo url", func() {
				BeforeEach(func() {
					deps, err = parser.Parse(bytes.NewBufferString(`
						github.com/nitrous-io/goop !git@github.com:foo/goop
					`))
				})

				It("parses and returns a slice containing one dependency item", func() {
					Expect(err).To(BeNil())
					Expect(deps).To(HaveLen(1))
					Expect(deps[0]).To(Equal(&parser.Dependency{
						Pkg: "github.com/nitrous-io/goop",
						URL: "git@github.com:foo/goop",
					}))
				})
			})

			Context("with a comment", func() {
				BeforeEach(func() {
					deps, err = parser.Parse(bytes.NewBufferString(`
						github.com/nitrous-io/goop // hello world
					`))
				})
			})

			Context("with unparseable garbage", func() {
				BeforeEach(func() {
					deps, err = parser.Parse(bytes.NewBufferString(`
						github.com/nitrous-io/goop (*@#&!@(*#)@$F@sdgu8$!
					`))
				})

				It("fails and returns parse error", func() {
					Expect(err).NotTo(BeNil())
					Expect(deps).To(BeNil())
				})
			})
		})

		Context("multiple entries", func() {
			BeforeEach(func() {
				deps, err = parser.Parse(bytes.NewBufferString(`
					github.com/nitrous-io/goop #09f0feb1b103933bd9985f0a85e01eeaad8d75c8

					github.com/gorilla/mux
					  github.com/gorilla/context #14f550f51af52180c2eefed15e5fd18d63c0a64a // future versions don't work
					github.com/foo/bar #ffffffffffffffffffffffffffffffffffffffff !git@github.com:baz/bar

					// don't upgrade this to 1.0.4
					github.com/hello/world !git@github.com:bye/world #v1.0.3 // I REPEAT, DON'T!
				`))
			})

			It("parses and returns a slice containing multiple dependency items", func() {
				Expect(err).To(BeNil())
				Expect(deps).To(HaveLen(5))
				Expect(deps[0]).To(Equal(&parser.Dependency{
					Pkg: "github.com/nitrous-io/goop",
					Rev: "09f0feb1b103933bd9985f0a85e01eeaad8d75c8",
				}))
				Expect(deps[1]).To(Equal(&parser.Dependency{
					Pkg: "github.com/gorilla/mux",
					Rev: "",
				}))
				Expect(deps[2]).To(Equal(&parser.Dependency{
					Pkg: "github.com/gorilla/context",
					Rev: "14f550f51af52180c2eefed15e5fd18d63c0a64a",
				}))
				Expect(deps[3]).To(Equal(&parser.Dependency{
					Pkg: "github.com/foo/bar",
					Rev: "ffffffffffffffffffffffffffffffffffffffff",
					URL: "git@github.com:baz/bar",
				}))
				Expect(deps[4]).To(Equal(&parser.Dependency{
					Pkg: "github.com/hello/world",
					Rev: "v1.0.3",
					URL: "git@github.com:bye/world",
				}))
			})
		})
	})
})


================================================
FILE: pkg/env/env.go
================================================
package env

import (
	"bytes"
	"os"
)

type Env map[string]string

func NewEnv() Env {
	e := Env{}
	osenv := os.Environ()

	for _, l := range osenv {
		kv := bytes.SplitN([]byte(l), []byte("="), 2)
		k := string(kv[0])
		if len(kv) == 2 {
			e[k] = string(kv[1])
		} else {
			e[k] = ""
		}
	}

	return e
}

func (e Env) Strings() []string {
	s := make([]string, 0, len(e))
	for k, v := range e {
		s = append(s, k+"="+v)
	}
	return s
}

func (e Env) Prepend(key string, val string) {
	oldv := e[key]
	if oldv == "" {
		e[key] = val
		return
	}
	e[key] = val + ":" + oldv
}


================================================
FILE: pkg/env/env_test.go
================================================
package env_test

import (
	"os"
	"testing"

	"github.com/nitrous-io/goop/pkg/env"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

func Test(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "env")
}

var _ = Describe("env", func() {
	var e env.Env

	BeforeEach(func() {
		os.Setenv("_GOOP_ENV_TEST_FOO", "foo")
		os.Setenv("_GOOP_ENV_TEST_BAR", "bar=bar bar")
		os.Setenv("_GOOP_ENV_TEST_EMPTY", "")

		e = env.NewEnv()
	})

	AfterEach(func() {
		os.Setenv("_GOOP_ENV_TEST_FOO", "")
		os.Setenv("_GOOP_ENV_TEST_BAR", "")
		os.Setenv("_GOOP_ENV_TEST_EMPTY", "")
	})

	Describe("NewEnv()", func() {
		It("returns a new env map using current env vars", func() {
			Expect(e["_GOOP_ENV_TEST_FOO"]).To(Equal("foo"))
			Expect(e["_GOOP_ENV_TEST_BAR"]).To(Equal("bar=bar bar"))
			Expect(e["_GOOP_ENV_TEST_EMPTY"]).To(BeEmpty())
		})
	})

	Describe("Strings()", func() {
		It("returns a copy of strings representing the env, in the form key=value", func() {
			s := e.Strings()
			Expect(s).To(ContainElement("_GOOP_ENV_TEST_FOO=foo"))
			Expect(s).To(ContainElement("_GOOP_ENV_TEST_BAR=bar=bar bar"))
			Expect(s).To(ContainElement("_GOOP_ENV_TEST_EMPTY="))
		})
	})

	Describe("Prepend()", func() {
		Context("when a given key has a value", func() {
			It("prepends new value to the existing value", func() {
				e.Prepend("_GOOP_ENV_TEST_FOO", "lol")
				Expect(e["_GOOP_ENV_TEST_FOO"]).To(Equal("lol:foo"))
				e.Prepend("_GOOP_ENV_TEST_FOO", "hello")
				Expect(e["_GOOP_ENV_TEST_FOO"]).To(Equal("hello:lol:foo"))
			})
		})

		Context("when a given key is empty", func() {
			It("sets new value", func() {
				e.Prepend("_GOOP_ENV_TEST_EMPTY", "foo")
				Expect(e["_GOOP_ENV_TEST_EMPTY"]).To(Equal("foo"))
				e.Prepend("_GOOP_ENV_TEST_EMPTY", "lol")
				Expect(e["_GOOP_ENV_TEST_EMPTY"]).To(Equal("lol:foo"))
			})
		})

		Context("when a given key does not exist", func() {
			BeforeEach(func() {
				delete(e, "_GOOP_ENV_TEST_EMPTY")
			})

			It("sets new value", func() {
				e.Prepend("_GOOP_ENV_TEST_EMPTY", "foo")
				Expect(e["_GOOP_ENV_TEST_EMPTY"]).To(Equal("foo"))
				e.Prepend("_GOOP_ENV_TEST_EMPTY", "lol")
				Expect(e["_GOOP_ENV_TEST_EMPTY"]).To(Equal("lol:foo"))
			})
		})
	})
})
Download .txt
gitextract__nt8qh5a/

├── .gitignore
├── Goopfile
├── LICENSE
├── Makefile
├── README.md
├── colors/
│   └── colors.go
├── goop/
│   ├── goget.go
│   ├── goop.go
│   ├── goop_test.go
│   ├── vcs.go
│   └── vcs_test.go
├── main.go
├── parser/
│   ├── dependency.go
│   ├── parser.go
│   └── parser_test.go
└── pkg/
    └── env/
        ├── env.go
        └── env_test.go
Download .txt
SYMBOL INDEX (50 symbols across 11 files)

FILE: colors/colors.go
  constant Reset (line 4) | Reset = "\033[0m"
  constant OK (line 5) | OK    = "\033[0;32m"
  constant Error (line 6) | Error = "\033[0;31m"
  constant Warn (line 7) | Warn  = "\033[0;33m"

FILE: goop/goget.go
  type DownloadRecorder (line 11) | type DownloadRecorder struct
    method Write (line 20) | func (d *DownloadRecorder) Write(p []byte) (n int, err error) {
    method Downloads (line 31) | func (d *DownloadRecorder) Downloads() []string {
  function NewDownloadRecorder (line 16) | func NewDownloadRecorder(writer io.Writer) *DownloadRecorder {
  method goGet (line 39) | func (g *Goop) goGet(pkgpath string, gopath string) ([]string, error) {

FILE: goop/goop.go
  type UnsupportedVCSError (line 19) | type UnsupportedVCSError struct
    method Error (line 23) | func (e *UnsupportedVCSError) Error() string {
  type Goop (line 27) | type Goop struct
    method patchedEnv (line 38) | func (g *Goop) patchedEnv(replaceGopath bool) env.Env {
    method PrintEnv (line 54) | func (g *Goop) PrintEnv() {
    method Exec (line 64) | func (g *Goop) Exec(name string, args ...string) error {
    method Install (line 78) | func (g *Goop) Install() error {
    method Update (line 93) | func (g *Goop) Update() error {
    method parseAndInstall (line 101) | func (g *Goop) parseAndInstall(goopfile *os.File, writeLockFile bool) ...
    method vendorDir (line 304) | func (g *Goop) vendorDir() string {
    method currentRev (line 308) | func (g *Goop) currentRev(vcsCmd string, path string) (string, error) {
    method clone (line 334) | func (g *Goop) clone(vcsCmd string, url string, clonePath string) error {
    method checkout (line 344) | func (g *Goop) checkout(vcsCmd string, path string, tag string) error {
    method command (line 363) | func (g *Goop) command(path string, name string, args ...string) *exec...
    method quietCommand (line 372) | func (g *Goop) quietCommand(path string, name string, args ...string) ...
  function NewGoop (line 34) | func NewGoop(dir string, stdin io.Reader, stdout io.Writer, stderr io.Wr...
  function repoForDep (line 379) | func repoForDep(dep *parser.Dependency) (*vcs.RepoRoot, error) {
  function pathExists (line 390) | func pathExists(path string) (bool, error) {

FILE: goop/goop_test.go
  function Test (line 10) | func Test(t *testing.T) {

FILE: goop/vcs.go
  function GuessVCS (line 10) | func GuessVCS(url string) string {
  function IdentifyVCS (line 27) | func IdentifyVCS(url string) string {
  function RepoRootForImportPathWithURLOverride (line 51) | func RepoRootForImportPathWithURLOverride(importPath string, url string)...

FILE: main.go
  function main (line 14) | func main() {
  function printUsage (line 71) | func printUsage() {
  constant usage (line 76) | usage = `

FILE: parser/dependency.go
  type Dependency (line 5) | type Dependency struct
    method String (line 11) | func (d *Dependency) String() string {

FILE: parser/parser.go
  type ParseError (line 10) | type ParseError struct
    method Error (line 27) | func (e *ParseError) Error() string {
  constant CommentOrPackage (line 17) | CommentOrPackage = iota
  constant URLOr (line 18) | URLOr
  constant TokenComment (line 22) | TokenComment = "//"
  constant TokenRev (line 23) | TokenRev     = "#"
  constant TokenURL (line 24) | TokenURL     = "!"
  function Parse (line 31) | func Parse(r io.Reader) ([]*Dependency, error) {

FILE: parser/parser_test.go
  function Test (line 12) | func Test(t *testing.T) {

FILE: pkg/env/env.go
  type Env (line 8) | type Env
    method Strings (line 27) | func (e Env) Strings() []string {
    method Prepend (line 35) | func (e Env) Prepend(key string, val string) {
  function NewEnv (line 10) | func NewEnv() Env {

FILE: pkg/env/env_test.go
  function Test (line 12) | func Test(t *testing.T) {
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (35K chars).
[
  {
    "path": ".gitignore",
    "chars": 616,
    "preview": ".vendor\n\n# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n*.test\n\n# Folders\n_obj/\n_test/\nb"
  },
  {
    "path": "Goopfile",
    "chars": 87,
    "preview": "github.com/onsi/ginkgo/ginkgo\ngithub.com/onsi/gomega\ncode.google.com/p/go.tools/go/vcs\n"
  },
  {
    "path": "LICENSE",
    "chars": 1090,
    "preview": "Copyright (c) 2014 Irrational Industries, Inc. d.b.a. Nitrous.IO.\n\nPermission is hereby granted, free of charge, to any "
  },
  {
    "path": "Makefile",
    "chars": 555,
    "preview": "NO_COLOR=\\033[0m\nOK_COLOR=\\033[32;01m\nERROR_COLOR=\\033[31;01m\nWARN_COLOR=\\033[33;01m\nBIN_NAME=goop\n\nall: build\n\nbuild:\n\t"
  },
  {
    "path": "README.md",
    "chars": 2280,
    "preview": "Goop\n====\n\n![Goopie](https://raw.githubusercontent.com/nitrous-io/goop/master/goopie.png)\n\nA dependency manager for Go ("
  },
  {
    "path": "colors/colors.go",
    "chars": 111,
    "preview": "package colors\n\nconst (\n\tReset = \"\\033[0m\"\n\tOK    = \"\\033[0;32m\"\n\tError = \"\\033[0;31m\"\n\tWarn  = \"\\033[0;33m\"\n)\n"
  },
  {
    "path": "goop/goget.go",
    "chars": 1187,
    "preview": "package goop\n\nimport (\n\t\"io\"\n\t\"os/exec\"\n\t\"regexp\"\n)\n\nvar goGetDownloadRe = regexp.MustCompile(`(?m)^(\\S+)\\s+\\(download\\)"
  },
  {
    "path": "goop/goop.go",
    "chars": 9184,
    "preview": "package goop\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"code.google.com/p/go.tools/go/vcs\"\n\n\t"
  },
  {
    "path": "goop/goop_test.go",
    "chars": 175,
    "preview": "package goop_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc Test(t *testing."
  },
  {
    "path": "goop/vcs.go",
    "chars": 1173,
    "preview": "package goop\n\nimport (\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"code.google.com/p/go.tools/go/vcs\"\n)\n\nfunc GuessVCS(url string) string {"
  },
  {
    "path": "goop/vcs_test.go",
    "chars": 2735,
    "preview": "package goop_test\n\nimport (\n\t\"github.com/nitrous-io/goop/goop\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n"
  },
  {
    "path": "main.go",
    "chars": 2060,
    "preview": "package main\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/nitrous-io/goop/colors\"\n\t\"github.com/"
  },
  {
    "path": "parser/dependency.go",
    "chars": 313,
    "preview": "package parser\n\nimport \"strings\"\n\ntype Dependency struct {\n\tPkg string\n\tRev string\n\tURL string\n}\n\nfunc (d *Dependency) S"
  },
  {
    "path": "parser/parser.go",
    "chars": 1402,
    "preview": "package parser\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n)\n\ntype ParseError struct {\n\tLineNum  uint\n\tLineText string\n\tM"
  },
  {
    "path": "parser/parser_test.go",
    "chars": 4424,
    "preview": "package parser_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/nitrous-io/goop/parser\"\n\t. \"github.com/onsi/ginkgo\"\n\t. \""
  },
  {
    "path": "pkg/env/env.go",
    "chars": 575,
    "preview": "package env\n\nimport (\n\t\"bytes\"\n\t\"os\"\n)\n\ntype Env map[string]string\n\nfunc NewEnv() Env {\n\te := Env{}\n\tosenv := os.Environ"
  },
  {
    "path": "pkg/env/env_test.go",
    "chars": 2216,
    "preview": "package env_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/nitrous-io/goop/pkg/env\"\n\t. \"github.com/onsi/ginkgo\"\n\t. \"githu"
  }
]

About this extraction

This page contains the full source code of the nitrous-io/goop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (29.5 KB), approximately 9.6k tokens, and a symbol index with 50 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!