├── LICENSE ├── README.md ├── cmd ├── root.go └── try │ └── main.go ├── runner ├── runner.go ├── shell │ └── shell.go ├── strategy │ ├── node.go │ └── strategy.go └── vcs │ └── vcs.go └── util └── util.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Zach Latta 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # try 2 | 3 | Try is a command-line utility that makes it easy to test-drive other people's projects without side effects. 4 | 5 | Usage: 6 | 7 | $ try [repo] 8 | 9 | Try will clone the repo, set up the environment, install dependencies, and then drop you into a shell where you can play around. Once you're done, exit the shell and everything will be deleted. 10 | 11 | Installation: 12 | 13 | $ go get github.com/zachlatta/try/cmd/try 14 | 15 | Examples: 16 | 17 | $ try https://github.com/ggbrw/boolr 18 | $ try https://github.com/shivammathur/IPpy 19 | $ try https://github.com/maxhallinan/my-clippings-to-json 20 | 21 | Currently supported environments: 22 | 23 | - Node / JavaScript 24 | 25 | If you'd like to add support for another environment, create a new strategy in [`runner/strategy/`](runner/strategy/). See [`runner/strategy/node.go`](runner/strategy/node.go) for an example. 26 | 27 | ## Why doesn't try use Docker? 28 | 29 | If you're like me, you've spent a bunch of time customizing your development environment and you want access to your custom dotfiles, non-traditional shell, and your editor when playing around with new repositories. 30 | 31 | Try opts for using temp directories over Docker so you have access to all of your tooling without having to re-install anything. 32 | 33 | ## How do you capitalize this thing? 34 | 35 | Try should be capitalized at the beginning of sentences, but lowercased everywhere else. 36 | 37 | Examples: 38 | 39 | > Why doesn't try support Docker? 40 | 41 | > Try doesn't support Docker because of the reason explained above. 42 | 43 | ## Feature Wishlist 44 | 45 | Run `git ls-files | xargs grep "TODO"` to get an up-to-date feature wishlist for try. 46 | 47 | ## License 48 | 49 | Try is licensed under the MIT license. See [`LICENSE`](LICENSE) for the full text. 50 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/zachlatta/try/runner" 9 | ) 10 | 11 | var RootCmd = &cobra.Command{ 12 | Use: "try [url]", 13 | Short: "Try running a given project", 14 | Run: func(md *cobra.Command, args []string) { 15 | if len(args) == 0 || len(args) > 1 { 16 | fmt.Fprintln(os.Stderr, "Please pass one argument.") 17 | os.Exit(1) 18 | } 19 | 20 | repoUrl := args[0] 21 | 22 | if err := runner.Run(repoUrl); err != nil { 23 | fmt.Fprintln(os.Stderr, err) 24 | os.Exit(1) 25 | } 26 | }, 27 | } 28 | 29 | func Execute() { 30 | if err := RootCmd.Execute(); err != nil { 31 | fmt.Fprintln(os.Stderr, err) 32 | os.Exit(1) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cmd/try/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/zachlatta/try/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/zachlatta/try/runner/shell" 9 | "github.com/zachlatta/try/runner/strategy" 10 | "github.com/zachlatta/try/runner/vcs" 11 | ) 12 | 13 | func tempDir(prefix string) (name string, err error) { 14 | return ioutil.TempDir("", prefix) 15 | } 16 | 17 | func Run(repoUrl string) error { 18 | dir, err := tempDir("try") 19 | if err != nil { 20 | return err 21 | } 22 | defer os.RemoveAll(dir) 23 | 24 | fmt.Println("Cloning repo...") 25 | if err := vcs.Clone(dir, repoUrl); err != nil { 26 | return err 27 | } 28 | 29 | strategy.InitAll(dir, repoUrl) 30 | 31 | env, err := strategy.SetupAll() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | if err := shell.Start(dir, env); err != nil { 37 | return err 38 | } 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /runner/shell/shell.go: -------------------------------------------------------------------------------- 1 | package shell 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/zachlatta/try/util" 10 | ) 11 | 12 | const defaultShell = "bash" 13 | 14 | func Start(dir string, env map[string]string) error { 15 | shellPath, err := exec.LookPath(defaultShell) 16 | if err != nil { 17 | return errors.New(defaultShell + " is not installed") 18 | } 19 | 20 | cmd := exec.Cmd{ 21 | Path: shellPath, 22 | Dir: dir, 23 | Stdout: os.Stdout, 24 | Stderr: os.Stderr, 25 | Stdin: os.Stdin, 26 | } 27 | 28 | if len(env) > 0 { 29 | fmt.Println("Starting", defaultShell+"...") 30 | fmt.Println() 31 | fmt.Println("> Please export the following environment variables:") 32 | fmt.Println() 33 | 34 | for _, env := range util.ConstructEnv(env, false) { 35 | fmt.Println("export", env) 36 | } 37 | 38 | fmt.Println() 39 | } 40 | 41 | if err := cmd.Run(); err != nil { 42 | return err 43 | } 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /runner/strategy/node.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | 7 | "github.com/zachlatta/try/util" 8 | ) 9 | 10 | type Node struct { 11 | dir string 12 | uri string 13 | env map[string]string 14 | } 15 | 16 | func (n *Node) Init(dir, uri string) { 17 | n.dir = dir 18 | n.uri = uri 19 | n.env = make(map[string]string) 20 | } 21 | 22 | // findPackageJsons finds all of the package.json files in the current and child 23 | // directories and returns a slice of paths of directories that have them. 24 | func (n Node) findPackageJsons() ([]string, error) { 25 | files, err := util.RecursiveList(n.dir) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | dirs := []string{} 31 | 32 | for _, file := range files { 33 | if filepath.Base(file) == "package.json" { 34 | dirs = append(dirs, filepath.Dir(file)) 35 | } 36 | } 37 | 38 | return dirs, nil 39 | } 40 | 41 | func (n Node) ShouldUse() (bool, error) { 42 | packageJsonDirs, err := n.findPackageJsons() 43 | if err != nil { 44 | return false, err 45 | } 46 | 47 | if len(packageJsonDirs) > 0 { 48 | return true, nil 49 | } 50 | 51 | return false, nil 52 | } 53 | 54 | func (n Node) Setup() (map[string]string, error) { 55 | fmt.Println("Installing Node dependencies...") 56 | 57 | packageJsonDirs, err := n.findPackageJsons() 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | for _, packageJsonDir := range packageJsonDirs { 63 | _, err := util.Run(packageJsonDir, n.env, "npm", "install") 64 | if err != nil { 65 | return nil, err 66 | } 67 | } 68 | 69 | return n.env, nil 70 | } 71 | 72 | func init() { 73 | strategies = append(strategies, &Node{}) 74 | } 75 | -------------------------------------------------------------------------------- /runner/strategy/strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | type Strategy interface { 4 | Init(dir, uri string) 5 | ShouldUse() (bool, error) 6 | Setup() (env map[string]string, err error) 7 | } 8 | 9 | var strategies = []Strategy{} 10 | 11 | func InitAll(dir, uri string) { 12 | for _, strategy := range strategies { 13 | strategy.Init(dir, uri) 14 | } 15 | } 16 | 17 | func SetupAll() (map[string]string, error) { 18 | env := make(map[string]string) 19 | 20 | for _, strategy := range strategies { 21 | shouldUse, err := strategy.ShouldUse() 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if shouldUse { 27 | additionalEnv, err := strategy.Setup() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | for key, val := range additionalEnv { 33 | env[key] = env[key] + val 34 | } 35 | } 36 | } 37 | 38 | return env, nil 39 | } 40 | -------------------------------------------------------------------------------- /runner/vcs/vcs.go: -------------------------------------------------------------------------------- 1 | package vcs 2 | 3 | import ( 4 | "github.com/zachlatta/try/util" 5 | ) 6 | 7 | func Clone(dir, uri string) error { 8 | // TODO: Support other VCS systems. 9 | return gitClone(dir, uri) 10 | } 11 | 12 | func gitClone(dir, uri string) error { 13 | _, err := util.Run(dir, nil, "git", "clone", uri, ".") 14 | if err != nil { 15 | return err 16 | } 17 | 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io/ioutil" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | func RecursiveList(dir string) (files []string, err error) { 14 | fileInfos, err := ioutil.ReadDir(dir) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | for _, fileInfo := range fileInfos { 20 | absolutePath := filepath.Join(dir, fileInfo.Name()) 21 | 22 | if fileInfo.IsDir() { 23 | childFiles, err := RecursiveList(absolutePath) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | files = append(files, childFiles...) 29 | } else { 30 | files = append(files, absolutePath) 31 | } 32 | } 33 | 34 | return files, nil 35 | } 36 | 37 | func ConstructEnv(env map[string]string, insertCurrentEnv bool) (newEnv []string) { 38 | mutEnv := make(map[string]string) 39 | 40 | if env != nil { 41 | for key, val := range env { 42 | mutEnv[key] = val 43 | } 44 | } 45 | 46 | if insertCurrentEnv { 47 | for _, rawEnv := range os.Environ() { 48 | split := strings.SplitN(rawEnv, "=", 2) 49 | 50 | name := split[0] 51 | val := split[1] 52 | 53 | if _, exists := mutEnv[name]; !exists { 54 | mutEnv[name] = val 55 | } 56 | } 57 | } 58 | 59 | for key, val := range env { 60 | newEnv = append(newEnv, key+"="+val) 61 | } 62 | 63 | return newEnv 64 | } 65 | 66 | func Run(dir string, env map[string]string, cmd string, args ...string) (output string, err error) { 67 | cmdPath, err := exec.LookPath(cmd) 68 | if err != nil { 69 | return "", err 70 | } 71 | 72 | outputBuf := &bytes.Buffer{} 73 | 74 | c := exec.Cmd{ 75 | Path: cmdPath, 76 | Args: append([]string{""}, args...), 77 | Dir: dir, 78 | Stdout: outputBuf, 79 | Stderr: outputBuf, 80 | Env: ConstructEnv(env, true), 81 | } 82 | 83 | if err := c.Run(); err != nil { 84 | return "", errors.New(outputBuf.String()) 85 | } 86 | 87 | return outputBuf.String(), nil 88 | } 89 | --------------------------------------------------------------------------------