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
====

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"))
})
})
})
})
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
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\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.