[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Gary Willoughby\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Vend\n\n**A small command line utility for fully vendoring module dependencies**\n\n---\n\n## Why?\n\n[Because Google have a different idea of what vendoring means than the rest\nof us.](https://github.com/golang/go/issues/26366) If you use the built-in\n`go mod vendor` command, it will only cherry pick certain files for inclusion\nin the vendor folder. This can cause problems when using\n[Cgo](https://blog.golang.org/c-go-cgo) because it [ignores C files that are\nnot in the package\ndirectory](https://github.com/golang/go/issues/26366#issuecomment-405683150).\nTests and examples for dependencies are ignored too.\n\nThis tool copies the entire dependency tree into the vendor folder like every\nother package manager does and how every sane developer would expect it to\nwork. It can be used safely in the `$GOPATH` or elsewhere.\n\nThis package expects that the new [module\nsystem](https://github.com/golang/go/wiki/Modules) [introduced in\nv1.11](https://golang.org/doc/go1.11) is being used.\n\n## What does it do?\n\nThis tool fully copies all files from your project's imported dependencies\ninto the `vendor` folder. This allows you to:\n\n1. Always have access to _all_ files in your dependencies, even if they go offline\n2. Always be able to build your project on a disconnected computer\n3. Always be able to run the tests or benchmarks for all your dependencies\n\n## Supported Go versions\n\n* v1.11+\n\n## Install\n\n```\n$ go get github.com/nomad-software/vend\n```\n\n## Usage\n\n```\n$ cd $GOPATH/mypackage\n$ vend\n```\n\n## Help\n\nRun the following command for help.\n\n```\n$ vend -help\n```\n"
  },
  {
    "path": "cli/cli.go",
    "content": "package cli\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n)\n\n// Options contains CLI arguments passed to the program.\ntype Options struct {\n\tHelp    bool\n\tPkgOnly bool\n}\n\n// ParseOptions parses the command line options and returns a struct filled with\n// the relevant options.\nfunc ParseOptions() Options {\n\tvar opt Options\n\n\tflag.BoolVar(&opt.Help, \"help\", false, \"Show help.\")\n\tflag.Parse()\n\n\treturn opt\n}\n\n// PrintUsage prints the usage of this tool.\nfunc (opt *Options) PrintUsage() {\n\tconst banner string = `                     _\n__   _____ _ __   __| |\n\\ \\ / / _ \\ '_ \\ / _' |\n \\ V /  __/ | | | (_| |\n  \\_/ \\___|_| |_|\\__,_|\n\n`\n\n\tfmt.Println(banner)\n\tfmt.Printf(\"A small command line utility for fully vendoring module dependencies\\n\\n\")\n\n\tflag.Usage()\n}\n"
  },
  {
    "path": "cli/cmd.go",
    "content": "package cli\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\n\t\"github.com/nomad-software/vend/output\"\n)\n\n// UpdateModule makes sure the module is updated ready to vendor the\n// dependencies.\nfunc UpdateModule() {\n\tvar commands = []string{\"tidy\", \"download\", \"vendor\"}\n\n\tfor _, command := range commands {\n\t\tcmd := exec.Command(\"go\", \"mod\", command)\n\n\t\tcmd.Env = buildEnv()\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Stderr = os.Stderr\n\n\t\terr := cmd.Run()\n\t\toutput.OnError(err, command)\n\t}\n}\n\n// ReadModJSON reads the module file and returns a Json string.\nfunc ReadModJSON() string {\n\tcmd := exec.Command(\"go\", \"mod\", \"edit\", \"-json\")\n\n\tcmd.Env = buildEnv()\n\tcmd.Stderr = os.Stderr\n\n\tb, err := cmd.Output()\n\toutput.OnError(err, \"Error running 'go mod edit'\")\n\n\treturn string(b)\n}\n\n// ReadDownloadJSON reads dependency information and returns a Json string.\nfunc ReadDownloadJSON() string {\n\tcmd := exec.Command(\"go\", \"mod\", \"download\", \"-json\")\n\n\tcmd.Env = buildEnv()\n\tcmd.Stderr = os.Stderr\n\n\tb, err := cmd.Output()\n\toutput.OnError(err, \"Error running 'go mod download'\")\n\n\treturn string(b)\n}\n\n// BuildEnv creates the environment in which to run the commands.\nfunc buildEnv() []string {\n\tenv := os.Environ()\n\tenv = append(env, \"GO111MODULE=on\")\n\treturn env\n}\n"
  },
  {
    "path": "file/dep.go",
    "content": "package file\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/nomad-software/vend/output\"\n)\n\n// ParseDownloadJSON parses the dependency file into a data structure.\nfunc ParseDownloadJSON(raw string) []Dep {\n\tdecoder := json.NewDecoder(strings.NewReader(raw))\n\tdata := make([]Dep, 0, 10)\n\n\tfor {\n\t\tvar dep Dep\n\t\terr := decoder.Decode(&dep)\n\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toutput.OnError(err, \"Error decoding dependency json\")\n\t\t}\n\n\t\tdata = append(data, dep)\n\t}\n\n\treturn data\n}\n\n// Dep represents parsed dependency json data.\ntype Dep struct {\n\tPath     string\n\tVersion  string\n\tError    string\n\tInfo     string\n\tGoMod    string\n\tZip      string\n\tDir      string\n\tSum      string\n\tGoModSum string\n}\n"
  },
  {
    "path": "file/file.go",
    "content": "package file\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\n\t\"github.com/nomad-software/vend/cli\"\n\t\"github.com/nomad-software/vend/output\"\n)\n\n// VendorDir represents a vendor directory.\ntype VendorDir struct {\n\tbasePath       string\n\tmodFileContent []byte\n\tmod            GoMod\n\tdeps           []Dep\n}\n\n// InitVendorDir creates a new vendor directory.\nfunc InitVendorDir() VendorDir {\n\twd, err := os.Getwd()\n\toutput.OnError(err, \"Error getting the current directory\")\n\n\tv := VendorDir{\n\t\tbasePath: path.Join(wd, \"vendor\"),\n\t\tmod:      ParseModJSON(cli.ReadModJSON()),\n\t\tdeps:     ParseDownloadJSON(cli.ReadDownloadJSON()),\n\t}\n\n\tif !v.exists(v.basePath) {\n\t\toutput.Fatal(\"vend: no dependencies to vendor\")\n\t}\n\n\treturn v\n}\n\n// CopyDependencies copies remote module level dependencies transitively.\nfunc (v *VendorDir) CopyDependencies() {\n\tv.clear()\n\n\tfor _, d := range v.deps {\n\t\tfmt.Printf(\"vend: copying %s (%s)\\n\", d.Path, d.Version)\n\t\tv.copy(d.Dir, v.vendPath(d.Path))\n\t}\n\n\tfor _, r := range v.mod.Replace {\n\t\tif r.Old.Path != r.New.Path {\n\t\t\tfmt.Printf(\"vend: replacing %s with %s\\n\", r.Old.Path, r.New.Path)\n\t\t\tnewPath := v.vendPath(r.New.Path)\n\t\t\toldPath := v.vendPath(r.Old.Path)\n\t\t\t// If the directory is in the vendor folder it was copied from the\n\t\t\t// module cache so we can just rename it. Otherwise it's a local\n\t\t\t// directory located somewhere else that needs copying in.\n\t\t\tif v.exists(newPath) {\n\t\t\t\tv.copy(newPath, oldPath)\n\t\t\t\tv.remove(newPath)\n\t\t\t} else {\n\t\t\t\tv.copy(r.New.Path, oldPath)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// exists checks if a file exists.\nfunc (v *VendorDir) exists(file string) bool {\n\t_, err := os.Stat(file)\n\treturn !os.IsNotExist(err)\n}\n\n// remove removes a path.\nfunc (v *VendorDir) remove(p string) {\n\terr := os.RemoveAll(p)\n\toutput.OnError(err, \"Error removing path\")\n}\n\n// vendPath creates a vendor directory path.\nfunc (v *VendorDir) vendPath(p string) string {\n\treturn path.Join(v.basePath, p)\n}\n\n// copyModFile internally copies and saves the modules.txt file.\nfunc (v *VendorDir) copyModFile() {\n\tvar err error\n\tv.modFileContent, err = ioutil.ReadFile(v.vendPath(\"modules.txt\"))\n\toutput.OnError(err, \"Error reading modules.txt\")\n}\n\n// writeModFile writes the modules.txt file into the vendor directory.\nfunc (v *VendorDir) writeModFile() {\n\terr := ioutil.WriteFile(v.vendPath(\"modules.txt\"), v.modFileContent, 0644)\n\toutput.OnError(err, \"Error saving modules.txt\")\n}\n\n// clear removes all dependencies from the vendor directory.\nfunc (v *VendorDir) clear() {\n\tv.copyModFile()\n\tv.remove(v.basePath)\n\n\terr := os.MkdirAll(v.basePath, 0755)\n\toutput.OnError(err, \"Error creating vendor directory\")\n\n\tv.writeModFile()\n}\n\n// copy will copy files and directories.\nfunc (v *VendorDir) copy(src string, dest string) {\n\tinfo, err := os.Lstat(src)\n\toutput.OnError(err, \"Error getting information about source\")\n\n\tif info.Mode()&os.ModeSymlink != 0 {\n\t\treturn // Completely ignore symlinks.\n\t}\n\n\tif info.IsDir() {\n\t\tv.copyDirectory(src, dest)\n\t} else {\n\t\tv.copyFile(src, dest)\n\t}\n}\n\n// copyDirectory will copy directories.\nfunc (v *VendorDir) copyDirectory(src string, dest string) {\n\terr := os.MkdirAll(dest, 0755)\n\toutput.OnError(err, \"Error creating directories\")\n\n\tcontents, err := ioutil.ReadDir(src)\n\toutput.OnError(err, \"Error reading source directory\")\n\n\tfor _, content := range contents {\n\t\ts := filepath.Join(src, content.Name())\n\t\td := filepath.Join(dest, content.Name())\n\t\tv.copy(s, d)\n\t}\n}\n\n// copyFile will copy files.\nfunc (v *VendorDir) copyFile(src string, dest string) {\n\terr := os.MkdirAll(filepath.Dir(dest), 0755)\n\toutput.OnError(err, \"Error creating directories\")\n\n\td, err := os.Create(dest)\n\toutput.OnError(err, \"Error creating file\")\n\tdefer d.Close()\n\n\ts, err := os.Open(src)\n\toutput.OnError(err, \"Error opening file\")\n\tdefer s.Close()\n\n\t_, err = io.Copy(d, s)\n\toutput.OnError(err, \"Error copying file\")\n}\n"
  },
  {
    "path": "file/mod.go",
    "content": "package file\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/nomad-software/vend/output\"\n)\n\n// ParseModJSON parses the mode file into a data structure.\nfunc ParseModJSON(raw string) GoMod {\n\tdata := GoMod{\n\t\tModule:  Module{},\n\t\tRequire: make([]Require, 0, 10),\n\t\tExclude: make([]Module, 0, 10),\n\t\tReplace: make([]Replace, 0, 10),\n\t\tRetract: make([]Retract, 0, 10),\n\t}\n\n\terr := json.Unmarshal([]byte(raw), &data)\n\toutput.OnError(err, \"Error parsing module json\")\n\n\treturn data\n}\n\n// GoMod represents parsed module json data.\ntype GoMod struct {\n\tModule  Module\n\tGo      string\n\tRequire []Require\n\tExclude []Module\n\tReplace []Replace\n\tRetract []Retract\n}\n\n// Module represents parsed module json data.\ntype Module struct {\n\tPath    string\n\tVersion string\n}\n\n// Require represents parsed module json data.\ntype Require struct {\n\tPath     string\n\tVersion  string\n\tIndirect bool\n}\n\n// Replace represents parsed module json data.\ntype Replace struct {\n\tOld Module\n\tNew Module\n}\n\n// Retract represents dependency version that are retracted.\ntype Retract struct {\n\tLow       string\n\tHigh      string\n\tRationale string\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/nomad-software/vend\n\ngo 1.16\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"github.com/nomad-software/vend/cli\"\n\t\"github.com/nomad-software/vend/file\"\n)\n\nfunc main() {\n\n\toptions := cli.ParseOptions()\n\n\tif options.Help {\n\t\toptions.PrintUsage()\n\n\t} else {\n\t\tcli.UpdateModule()\n\n\t\tdir := file.InitVendorDir()\n\t\tdir.CopyDependencies()\n\t}\n}\n"
  },
  {
    "path": "output/output.go",
    "content": "package output\n\nimport (\n\t\"fmt\"\n\t\"os\"\n)\n\n// OnError prints an error if err is not nil and exits the program.\nfunc OnError(err error, text string) {\n\tif err != nil {\n\t\tFatal(\"%s:, %s\", text, err.Error())\n\t}\n}\n\n// Fatal prints an error and exits the program.\nfunc Fatal(format string, args ...interface{}) {\n\tfmt.Fprintf(os.Stderr, format+\"\\n\", args...)\n\tos.Exit(1)\n}\n\n// Info prints information.\nfunc Info(format string, args ...interface{}) {\n\tfmt.Printf(format+\"\\n\", args...)\n}\n"
  }
]