pax_global_header00006660000000000000000000000064125727646250014532gustar00rootroot0000000000000052 comment=28ec88118f323259449e6086e941cefd4fbc4495 direnv-2.7.0/000077500000000000000000000000001257276462500130275ustar00rootroot00000000000000direnv-2.7.0/.gitignore000066400000000000000000000001001257276462500150060ustar00rootroot00000000000000.direnv /direnv /direnv.test /dist /site /test/allow man/*.html direnv-2.7.0/.nojekyll000066400000000000000000000000001257276462500146450ustar00rootroot00000000000000direnv-2.7.0/.travis.yml000066400000000000000000000015461257276462500151460ustar00rootroot00000000000000language: go go: - 1.4 sudo: false script: make test branches: except: - gh-pages before_deploy: - PATH="$HOME/gopath/bin:$PATH" make dist deploy: provider: releases api_key: secure: aIw9Rt8yY8hQu+KFqgESP1v228sX709zTjWIA+mWW1NG5O6m99pjB8UDhar1SBtwZ8ovf/pYfvox4MFEzTsP3cJ9YKrK2HlodZZy46l7qaCpplhd+GIY7JOJlWgeUK5smgaVq60ITvTGoJtcLLX+TedkfaKfQsehIg6Sob4WhQ8= file: - dist/direnv.darwin-386 - dist/direnv.darwin-amd64 - dist/direnv.linux-386 - dist/direnv.linux-amd64 - dist/direnv.linux-arm - dist/direnv.freebsd-386 - dist/direnv.freebsd-amd64 - dist/direnv.openbsd-386 - dist/direnv.openbsd-amd64 - dist/direnv.windows-386.exe - dist/direnv.windows-amd64.exe - dist/direnv.freebsd-arm - dist/direnv.netbsd-386 - dist/direnv.netbsd-amd64 - dist/direnv.netbsd-arm skip_cleanup: true on: tags: true all_branches: true direnv-2.7.0/CHANGELOG.md000066400000000000000000000120751257276462500146450ustar00rootroot00000000000000 2.7.0 / 2015-08-08 ================== * NEW: use_nix() helper to stdlib. Thanks @gfxmonk * FIX: Added SHELLOPTS to ignored vars. Thanks @fernandomora * FIX: Removed shellcheck offenses in the stdlib, better escaping * FIX: typos. Thanks @camelpunch, @oppegard 2.6.1 / 2015-06-23 ================== * FIX: source_env handles missing .envrc gracefully. Thanks @gerhard * FIX: Empty variable as unloading in Vim. Thanks @p0deje * FIX: Corrected spelling mistake in deny command. Thanks @neanias 2.6.0 / 2015-02-15 ================== * NEW: tcsh is now supported ! Thanks @bbense * CHANGE: `direnv dump` now ignores `BASH_FUNC_` exports. Thanks @gfxmonk * CHANGE: Interactive input during load is now possible. Thanks @toao * FIX: allow workaround for tmux users: `alias tmux='direnv exec / tmux'` * FIX: hardened fish shell escaping thanks to @gfxmonk Thanks @bbense @vially and @dadooda for corrections in the docs 2.5.0 / 2014-11-04 ================== * NEW: Use a different virtualenv per python versions for easier version switching. Eg: ./.direnv/python-${python_version} * NEW: Makes `layout python3` a shortcut for `layout python python3`. Thanks @ghickman ! * NEW: Allows to specify which executable of python to use in `layout_python` * CHANGE: `layout python` now unsets $PYTHONHOME to better mimic virtualenv * CHANGE: Don't make virtualenvs relocatable. Fixes #137 * OTHER: Use Travis to push release builds to github 2.4.0 / 2014-06-15 ================== * NEW: Try to detect an editor in the PATH if EDITOR is not set. * NEW: Preliminary support for vim * NEW: New site: put the doc inside the project so it stays in sync * NEW: Support for Cygwin - Thanks @CMCDragonkai ! * NEW: Allow to disable logging by setting an empty `DIRENV_LOG_FORMAT` * NEW: stdlib `layout perl`. Thanks @halkeye ! * CHANGE: layout ruby: share the gem home starting from rubygems v2.2.0 * CHANGE: Allow arbitrary number of args in `log_status` * CHANGE: Bump command timeout to 5 seconds * FIX: Adds selected bash executable in `direnv status` * FIX: man changes, replaced abandonned ronn by md2man * FIX: `make install` was creating a ./bin directory * FIX: issue #114 - work for blank envs. Thanks @pwaller ! * FIX: man pages warning. Thanks @punitagrawal ! * FIX: Multi-arg EDITOR was broken #108 * FIX: typos in doc. Thanks @HeroicEric and @lmarlow ! * FIX: If two paths don't have a common ancestors, don't make them relative. * FIX: missing doc on layered .envrc. Thanks @take ! 2.3.0 / 2014-02-06 ================== * NEW: DIRENV_LOG_FORMAT environment variable can be used tocontrol log formatting * NEW: `direnv exec [DIR] ` to execute programs with an .envrc context * CHANGE: layout_python now tries to make your virtualenv relocatable * CHANGE: the export diff is not from the old env, not the current env * CHANGE: layout_go now also adds $PWD/bin in the PATH * FIX: Hides the DIRENV_ variables in the output diff. Fixes #94 * FIX: Makes sure the path used in the allow hash is absolute. See #95 * FIX: Set the executable bit on direnv on install * FIX: Some bash installs had a parse error in the hook. 2.2.1 / 2014-01-12 ================== The last release was heavily broken. Ooops ! * FIX: Refactored the whole export and diff mechanism. Fixes #92 regression. * CHANGE: DIRENV_BACKUP has been renamed to DIRENV_DIFF 2.2.0 / 2014-01-11 ================== Restart your shells on upgrade, the format of DIRENV_BACKUP has changed and is incompatible with previous versions. * NEW: `direnv_load ` stdlib function * CHANGE: Only backup the diff of environments. Fixes #82 * CHANGE: Renames `$DIRENV_PATH` to `$direnv` in the stdlib. * CHANGE: Allow/Deny mechanism now includes the path to make it more secure. * CHANGE: `direnv --help` is an alias to `direnv help` * CHANGE: more consistent log outputs and error messages * CHANGE: `direnv edit` only auto-allows the .envrc if it's mtime has changed. * CHANGE: Fixes old bash (OSX) segfault in some cases. See #81 * CHANGE: The stdlib `dotenv` now supports more .env syntax * FIX: Restore the environment properly after loading errors. 2.1.0 / 2013-11-10 ================== * Added support for the fish shell. See README.md for install instructions. * Stop recommending using $0 to detect the shell. Fixes #64. * Makes the zsh hook resistant to double-hooking. * Makes the bash hook resistant to double-hooking. * More precise direnv allow error message. Fixes #72 2.0.1 / 2013-07-27 ================== * Fixes shell detection corner case 2.0.0 / 2013-06-16 ================== When upgrading from direnv 1.x make sure to restart your shell. The rest is relatively backward-compatible. * changed the execution model. Everything is in a single static executable * most of the logic has been rewritten in Go * robust shell escaping (supports UTF-8 in env vars) * robust eval/export loop, avoids retrys on every prompt if there is an error * stdlib: added the `dotenv [PATH]` command to load .env files * command: added `direnv reload` to force-reload your environment direnv-2.7.0/CNAME000066400000000000000000000000131257276462500135670ustar00rootroot00000000000000direnv.net direnv-2.7.0/LICENSE.md000066400000000000000000000021671257276462500144410ustar00rootroot00000000000000(The MIT License) Copyright (c) 2014 zimbatm and [contributors](https://github.com/direnv/direnv/graphs/contributors) 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. direnv-2.7.0/Makefile000066400000000000000000000014121257276462500144650ustar00rootroot00000000000000DESTDIR ?= /usr/local MAN_MD = $(wildcard man/*.md) ROFFS = $(MAN_MD:.md=) .PHONY: all man html test install dist #all: build man test all: build man build: direnv stdlib.go: stdlib.sh cat $< | ./script/str2go main STDLIB $< > $@ direnv: *.go go fmt go build -o direnv clean: rm -f direnv %.1: %.1.md @which md2man-roff >/dev/null || (echo "Could not generate man page because md2man is missing, gem install md2man"; false) md2man-roff $< > $@ man: $(ROFFS) test: build go test ./test/direnv-test.sh install: all install -d $(DESTDIR)/bin install -d $(DESTDIR)/share/man/man1 install direnv $(DESTDIR)/bin cp -R man/*.1 $(DESTDIR)/share/man/man1 dist: go get github.com/mitchellh/gox gox -build-toolchain gox -output "dist/{{.Dir}}.{{.OS}}-{{.Arch}}" direnv-2.7.0/README.md000066400000000000000000000116101257276462500143050ustar00rootroot00000000000000direnv -- Unclutter your .profile ================================= `direnv` is an environment switcher for the shell. It knows how to hook into bash, zsh, tcsh and fish shell to load or unload environment variables depending on the current directory. This allows to have project-specific environment variables and not clutter the "~/.profile" file. Before each prompt it checks for the existence of an ".envrc" file in the current and parent directories. If the file exists (and authorized), it is loaded into a bash sub-shell and all exported variables are then captured by direnv and then made available the current shell. Because direnv is compiled into a single static executable it is fast enough to be unnoticeable on each prompt. It is also language agnostic and can be used to build solutions similar to rbenv, pyenv, phpenv, ... ## Example ``` $ cd ~/my_project $ echo ${FOO-nope} nope $ echo export FOO=foo > .envrc .envrc is not allowed $ direnv allow . direnv: reloading direnv: loading .envrc direnv export: +FOO $ echo ${FOO-nope} foo $ cd .. direnv: unloading direnv export: ~PATH $ echo ${FOO-nope} nope ``` ## Install ### From source Dependencies: make, golang ```bash git clone https://github.com/direnv/direnv cd direnv make install # or symlink ./direnv into the $PATH ``` ### Packaged There's package definitions on Homebrew, Arch's AUR and NixOS's nixpkgs. Links to binary builds are also available on [each release](https://github.com/direnv/direnv/releases). ## Setup For direnv to work properly it needs to be hooked into the shell. Each shell has its own extension mechanism: ### BASH Add the following line at the end of the "~/.bashrc" file: ```sh eval "$(direnv hook bash)" ``` Make sure it appears even after rvm, git-prompt and other shell extensions that manipulate the prompt. ### ZSH Add the following line at the end of the "~/.zshrc" file: ```sh eval "$(direnv hook zsh)" ``` ### FISH Add the following line at the end of the "~/.config/fish/config.fish" file: ```fish eval (direnv hook fish) ``` ### TCSH Add the following line at the end of the "~/.cshrc" file: ```sh eval `direnv hook tcsh` ``` ## Usage In some target folder, create an ".envrc" file and add some export(1) directives in it. On the next prompt you will notice that direnv complains about the ".envrc" being blocked. This is the security mechanism to avoid loading new files automatically. Otherwise any git repo that you pull, or tar archive that you unpack, would be able to wipe your hard drive once you `cd` into it. So here we are pretty sure that it won't do anything bad. Type `direnv allow .` and watch direnv loading your new environment. Note that `direnv edit .` is a handy shortcut that opens the file in your $EDITOR and automatically allows it if the file's modification time has changed. Now that the environment is loaded you can notice that once you `cd` out of the directory it automatically gets unloaded. If you `cd` back into it it's loaded again. That's the base of the mechanism that allows you to build cool things. ### The stdlib Exporting variables by hand is a bit repetitive so direnv provides a set of utility functions that are made available in the context of the ".envrc" file. As an example, the `PATH_add` function is used to expand and prepend a path to the $PATH environment variable. Instead of `export $PATH=$PWD/bin:$PATH` you can write `PATH_add bin`. It's shorter and avoid a common mistake where `$PATH=bin`. To find the documentation for all available functions check the direnv-stdlib(1) man page. It's also possible to create your own extensions by creating a bash file at "~/.config/direnv/direnvrc" or "~/.direnvrc". This file is loaded before your ".envrc" and thus allows you to make your own extensions to direnv. #### Loading layered .envrc Lets say you have the following structure: - "/a/.envrc" - "/a/b/.envrc" If you add the following line in "/a/b/.envrc", you can load both of the ".envrc" when you are in `/a/b`: ```sh source_env .. ``` ## Similar projects * [Environment Modules](http://modules.sourceforge.net/) - one of the oldest (in a good way) environment loading system * [autoenv](https://github.com/kennethreitz/autoenv) - lightweight, doesn't support unloads ## Contribute Bug reports, contributions and forks are welcome. All bugs or other forms of discussion happen on There is also a wiki available where you can share your usage patterns or other tips and tricks Or drop by on [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/direnv/direnv?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) to have a chat. [![Build Status](https://api.travis-ci.org/direnv/direnv.png?branch=master)](http://travis-ci.org/direnv/direnv) ## COPYRIGHT Copyright (C) 2014 shared by all [contributors](https://github.com/direnv/direnv/graphs/contributors) under the MIT licence. direnv-2.7.0/cmd_allow.go000066400000000000000000000011231257276462500153140ustar00rootroot00000000000000package main import ( "fmt" "os" ) // `direnv allow [PATH_TO_RC]` var CmdAllow = &Cmd{ Name: "allow", Desc: "Grants direnv to load the given .envrc", Args: []string{"[PATH_TO_RC]"}, Fn: func(env Env, args []string) (err error) { var rcPath string var config *Config if len(args) > 1 { rcPath = args[1] } else { if rcPath, err = os.Getwd(); err != nil { return } } if config, err = LoadConfig(env); err != nil { return } rc := FindRC(rcPath, config.AllowDir()) if rc == nil { return fmt.Errorf(".envrc file not found") } return rc.Allow() }, } direnv-2.7.0/cmd_apply_dump.go000066400000000000000000000014661257276462500163620ustar00rootroot00000000000000package main import ( "fmt" "io/ioutil" ) // `direnv apply_dump FILE` var CmdApplyDump = &Cmd{ Name: "apply_dump", Desc: "Accepts a filename containing `direnv dump` output and generates a series of bash export statements to apply the given env", Args: []string{"FILE"}, Private: true, Fn: func(env Env, args []string) (err error) { if len(args) < 2 { return fmt.Errorf("Not enough arguments") } if len(args) > 2 { return fmt.Errorf("Too many arguments") } filename := args[1] dumped, err := ioutil.ReadFile(filename) if err != nil { return err } dumpedEnv, err := LoadEnv(string(dumped)) if err != nil { return err } diff := env.Diff(dumpedEnv) exports := diff.ToShell(BASH) _, err = fmt.Println(exports) if err != nil { return err } return }, } direnv-2.7.0/cmd_deny.go000066400000000000000000000011251257276462500151370ustar00rootroot00000000000000package main import ( "fmt" "os" ) // `direnv deny [PATH_TO_RC]` var CmdDeny = &Cmd{ Name: "deny", Desc: "Revokes the authorization of a given .envrc", Args: []string{"[PATH_TO_RC]"}, Fn: func(env Env, args []string) (err error) { var rcPath string var config *Config if len(args) > 1 { rcPath = args[1] } else { if rcPath, err = os.Getwd(); err != nil { return } } if config, err = LoadConfig(env); err != nil { return } rc := FindRC(rcPath, config.AllowDir()) if rc == nil { return fmt.Errorf(".envrc file not found") } return rc.Deny() }, } direnv-2.7.0/cmd_dotenv.go000066400000000000000000000032541257276462500155040ustar00rootroot00000000000000package main import ( "fmt" "io/ioutil" "regexp" "strings" ) var DOTENV_REG = regexp.MustCompile("(?:export\\s+)?([\\w\\.]+)(?:\\s*=\\s*|:\\s+?)(.*)") var DOTENV_LF_REG = regexp.MustCompile("\\\\n") var DOTENV_ESC_REG = regexp.MustCompile("\\\\.") func ParseDotEnv(data string) Env { var dotenv = make(Env) result := DOTENV_REG.FindAllStringSubmatch(data, -1) for _, match := range result { key := match[1] value := strings.TrimSpace(match[2]) if value[0:1] == "'" && value[len(value)-1:] == "'" { value = value[1 : len(value)-1] } else if value[0:1] == `"` && value[len(value)-1:] == `"` { value = value[1 : len(value)-1] value = DOTENV_LF_REG.ReplaceAllString(value, "\n") value = DOTENV_ESC_REG.ReplaceAllStringFunc(value, func(str string) string { return str[1:2] }) } dotenv[key] = value } return dotenv } // `direnv private dotenv [SHELL [PATH_TO_DOTENV]]` // Transforms a .env file to evaluatable `export KEY=PAIR` statements. // // See: https://github.com/bkeepers/dotenv and // https://github.com/ddollar/foreman var CmdDotEnv = &Cmd{ Name: "dotenv", Desc: "Transforms a .env file to evaluatable `export KEY=PAIR` statements", Args: []string{"[SHELL]", "[PATH_TO_DOTENV]"}, Private: true, Fn: func(env Env, args []string) (err error) { var shell Shell var target string if len(args) > 1 { shell = DetectShell(args[1]) } else { shell = BASH } if len(args) > 2 { target = args[2] } if target == "" { target = ".env" } var data []byte if data, err = ioutil.ReadFile(target); err != nil { return } env = ParseDotEnv(string(data)) str := env.ToShell(shell) fmt.Println(str) return }, } direnv-2.7.0/cmd_dotenv_test.go000066400000000000000000000032621257276462500165420ustar00rootroot00000000000000package main import ( "testing" ) // See the reference implementation: // https://github.com/bkeepers/dotenv/blob/master/lib/dotenv/environment.rb // TODO: support shell variable expansions // TODO: support comments at the end of a line const TEST_EXPORTED = `export OPTION_A=2 export OPTION_B='\n' ` func TestDotEnvExported(t *testing.T) { env := ParseDotEnv(TEST_EXPORTED) if env["OPTION_A"] != "2" { //t.Fail() } if env["OPTION_B"] != "\\n" { t.Fail() } } const TEST_PLAIN = `OPTION_A=1 OPTION_B=2 OPTION_C= 3 OPTION_D =4 OPTION_E = 5 ` func TestDotEnvPlain(t *testing.T) { env := ParseDotEnv(TEST_PLAIN) if env["OPTION_A"] != "1" { t.Fail() } if env["OPTION_B"] != "2" { t.Fail() } if env["OPTION_C"] != "3" { t.Fail() } if env["OPTION_D"] != "4" { t.Fail() } if env["OPTION_E"] != "5" { t.Fail() } } const TEST_QUOTED = `OPTION_A='1' OPTION_B='2' OPTION_C='' OPTION_D='\n' OPTION_E="1" OPTION_F="2" OPTION_G="" OPTION_H="\n" ` func TestDotEnvQuoted(t *testing.T) { env := ParseDotEnv(TEST_QUOTED) if env["OPTION_A"] != "1" { t.Fail() } if env["OPTION_B"] != "2" { t.Fail() } if env["OPTION_C"] != "" { t.Fail() } if env["OPTION_D"] != "\\n" { t.Fail() } if env["OPTION_E"] != "1" { t.Fail() } if env["OPTION_F"] != "2" { t.Fail() } if env["OPTION_G"] != "" { t.Fail() } if env["OPTION_H"] != "\n" { t.Fail() } } const TEST_YAML = `OPTION_A: 1 OPTION_B: '2' OPTION_C: '' OPTION_D: '\n' ` func TestDotEnvYAML(t *testing.T) { env := ParseDotEnv(TEST_YAML) if env["OPTION_A"] != "1" { t.Fail() } if env["OPTION_B"] != "2" { t.Fail() } if env["OPTION_C"] != "" { t.Fail() } if env["OPTION_D"] != "\\n" { t.Fail() } } direnv-2.7.0/cmd_dump.go000066400000000000000000000004171257276462500151500ustar00rootroot00000000000000package main import ( "fmt" ) // `direnv dump` var CmdDump = &Cmd{ Name: "dump", Desc: "Used to export the inner bash state at the end of execution", Private: true, Fn: func(env Env, args []string) (err error) { fmt.Println(env.Serialize()) return }, } direnv-2.7.0/cmd_edit.go000066400000000000000000000034501257276462500151300ustar00rootroot00000000000000package main import ( "fmt" "os" "os/exec" "path/filepath" "strings" ) // `direnv edit [PATH_TO_RC]` var CmdEdit = &Cmd{ Name: "edit", Desc: `Opens PATH_TO_RC or the current .envrc into an $EDITOR and allow the file to be loaded afterwards.`, Args: []string{"[PATH_TO_RC]"}, NoWait: true, Fn: func(env Env, args []string) (err error) { var config *Config var rcPath string var mtime int64 var foundRC *RC if config, err = LoadConfig(env); err != nil { return } foundRC = config.FindRC() if foundRC != nil { mtime = foundRC.mtime } if len(args) > 1 { rcPath = args[1] fi, _ := os.Stat(rcPath) if fi != nil && fi.IsDir() { rcPath = filepath.Join(rcPath, ".envrc") } } else { if foundRC == nil { return fmt.Errorf(".envrc not found. Use `direnv edit .` to create a new envrc in the current directory.") } rcPath = foundRC.path } editor := env["EDITOR"] if editor == "" { log_error("$EDITOR not found.") editor = detectEditor(env["PATH"]) if editor == "" { err = fmt.Errorf("Could not find a default editor in the PATH") return } } cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("%s %s", editor, rcPath)) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { return } foundRC = FindRC(rcPath, config.AllowDir()) if foundRC != nil && foundRC.mtime > mtime { foundRC.Allow() } return }, } // Utils var EDITORS = [][]string{ {"subl", "-w"}, {"mate", "-w"}, {"open", "-t", "-W"}, // Opens with the default text editor on mac {"nano"}, {"vim"}, {"emacs"}, } func detectEditor(pathenv string) string { for _, editor := range EDITORS { if _, err := lookPath(editor[0], pathenv); err == nil { return strings.Join(editor, " ") } } return "" } direnv-2.7.0/cmd_exec.go000066400000000000000000000026501257276462500151300ustar00rootroot00000000000000package main import ( "fmt" "os" "path/filepath" "syscall" ) // `direnv exec [DIR] ...` var CmdExec = &Cmd{ Name: "exec", Desc: "Executes a command after loading the first .envrc found in DIR", Args: []string{"[DIR]", "COMMAND", "[...ARGS]"}, Fn: func(env Env, args []string) (err error) { var ( backupDiff *EnvDiff config *Config newEnv Env rcPath string command string ) if len(args) < 2 { return fmt.Errorf("missing DIR and COMMAND arguments") } rcPath = filepath.Clean(args[1]) fi, err := os.Stat(rcPath) if err != nil { return } if fi.IsDir() { if len(args) < 3 { return fmt.Errorf("missing COMMAND argument") } command = args[2] args = args[2:] } else { command = rcPath rcPath = filepath.Dir(rcPath) args = args[1:] } if config, err = LoadConfig(env); err != nil { return } rc := FindRC(rcPath, config.AllowDir()) // Restore pristine environment if needed if backupDiff, err = config.EnvDiff(); err == nil { backupDiff.Reverse().Patch(env) } delete(env, DIRENV_DIR) delete(env, DIRENV_MTIME) delete(env, DIRENV_DIFF) // Load the rc if rc != nil { if newEnv, err = rc.Load(config, env); err != nil { return } } else { newEnv = env } command, err = lookPath(command, newEnv["PATH"]) if err != nil { return } err = syscall.Exec(command, args, newEnv.ToGoEnv()) return }, } direnv-2.7.0/cmd_expand_path.go000066400000000000000000000016631257276462500165020ustar00rootroot00000000000000package main import ( "flag" "fmt" "os" "path/filepath" ) func expandPath(path, relTo string) string { if filepath.IsAbs(path) { return path } return filepath.Clean(filepath.Join(relTo, path)) } // `direnv expand_path PATH [REL_TO]` var CmdExpandPath = &Cmd{ Name: "expand_path", Desc: "Transforms a PATH to an absolute path to REL_TO or $PWD", Args: []string{"PATH", "[REL_TO]"}, Private: true, Fn: func(env Env, args []string) (err error) { var path string flagset := flag.NewFlagSet(args[0], flag.ExitOnError) flagset.Parse(args[1:]) path = flagset.Arg(0) if path == "" { return fmt.Errorf("PATH missing") } if !filepath.IsAbs(path) { wd, err := os.Getwd() if err != nil { return err } relTo := flagset.Arg(1) if relTo == "" { relTo = wd } else { relTo = expandPath(relTo, wd) } path = expandPath(path, relTo) } _, err = fmt.Println(path) return }, } direnv-2.7.0/cmd_export.go000066400000000000000000000051741257276462500155310ustar00rootroot00000000000000package main import ( "fmt" "path/filepath" "sort" "strings" ) // `direnv export $0` var CmdExport = &Cmd{ Name: "export", Desc: "loads an .envrc and prints the diff in terms of exports", Args: []string{"SHELL"}, Private: true, Fn: func(env Env, args []string) (err error) { var oldEnv Env = env.Copy() var newEnv Env var loadedRC *RC var foundRC *RC var config *Config var target string if len(args) > 1 { target = args[1] } shell := DetectShell(target) if shell == nil { return fmt.Errorf("Unknown target shell '%s'", target) } if config, err = LoadConfig(env); err != nil { return } loadedRC = config.LoadedRC() foundRC = config.FindRC() loadRC := func() { newEnv, err = foundRC.Load(config, oldEnv) } //fmt.Fprintf(os.Stderr, "%v %v\n", loadedRC, foundRC) if loadedRC == nil { if foundRC == nil { // We're done here. return nil } loadRC() } else { var backupDiff *EnvDiff if backupDiff, err = config.EnvDiff(); err != nil { err = fmt.Errorf("EnvDiff() failed: %q", err) goto error } oldEnv = backupDiff.Reverse().Patch(env) if foundRC == nil { log_status(env, "unloading") newEnv = oldEnv.Copy() delete(newEnv, DIRENV_DIR) delete(newEnv, DIRENV_MTIME) delete(newEnv, DIRENV_DIFF) } else if loadedRC.path != foundRC.path { loadRC() } else if loadedRC.mtime != foundRC.mtime { loadRC() } else { // Nothing to do. Env is loaded and hasn't changed return nil } } error: if err != nil { newEnv = oldEnv.Copy() delete(oldEnv, DIRENV_DIR) delete(oldEnv, DIRENV_MTIME) delete(oldEnv, DIRENV_DIFF) if foundRC != nil { delete(newEnv, DIRENV_DIFF) // This should be nearby rc.Load()'s similar statement newEnv[DIRENV_DIR] = "-" + filepath.Dir(foundRC.path) newEnv[DIRENV_MTIME] = fmt.Sprintf("%d", foundRC.mtime) newEnv[DIRENV_DIFF] = oldEnv.Diff(newEnv).Serialize() } } oldDiff := oldEnv.Diff(newEnv) if oldDiff.Any() { var out []string for key, _ := range oldDiff.Prev { _, ok := oldDiff.Next[key] if !ok && !direnvKey(key) { out = append(out, "-"+key) } } for key := range oldDiff.Next { _, ok := oldDiff.Prev[key] if direnvKey(key) { continue } if ok { out = append(out, "~"+key) } else { out = append(out, "+"+key) } } sort.Strings(out) if len(out) > 0 { log_status(env, "export %s", strings.Join(out, " ")) } } diff := env.Diff(newEnv) str := diff.ToShell(shell) fmt.Print(str) return }, } func direnvKey(key string) bool { return strings.HasPrefix(key, "DIRENV_") } direnv-2.7.0/cmd_help.go000066400000000000000000000014111257276462500151260ustar00rootroot00000000000000package main import ( "fmt" "strings" ) // `direnv help` var CmdHelp = &Cmd{ Name: "help", Desc: "shows this help", Args: []string{"[SHOW_PRIVATE]"}, Aliases: []string{"--help"}, Fn: func(env Env, args []string) (err error) { var showPrivate = len(args) > 1 fmt.Printf(`direnv v%s Usage: direnv COMMAND [...ARGS] Available commands ------------------ `, VERSION) for _, cmd := range CmdList { var opts string if len(cmd.Args) > 0 { opts = " " + strings.Join(cmd.Args, " ") } if cmd.Private { if showPrivate { fmt.Printf("*%s%s:\n %s\n", cmd.Name, opts, cmd.Desc) } } else { fmt.Printf("%s%s:\n %s\n", cmd.Name, opts, cmd.Desc) } } if showPrivate { fmt.Println("* = private commands") } return }, } direnv-2.7.0/cmd_hook.go000066400000000000000000000006521257276462500151440ustar00rootroot00000000000000package main import ( "fmt" ) // `direnv hook $0` var CmdHook = &Cmd{ Name: "hook", Desc: "Used to setup the shell hook", Args: []string{"SHELL"}, Fn: func(env Env, args []string) (err error) { var target string if len(args) > 1 { target = args[1] } shell := DetectShell(target) if shell == nil { return fmt.Errorf("Unknown target shell '%s'", target) } fmt.Println(shell.Hook()) return }, } direnv-2.7.0/cmd_reload.go000066400000000000000000000005621257276462500154520ustar00rootroot00000000000000package main import ( "fmt" ) var CmdReload = &Cmd{ Name: "reload", Desc: "triggers an env reload", Fn: func(env Env, args []string) error { config, err := LoadConfig(env) if err != nil { return err } foundRC := config.FindRC() if foundRC != nil { return foundRC.Touch() } else { return fmt.Errorf(".envrc not found") } return nil }, } direnv-2.7.0/cmd_status.go000066400000000000000000000017731257276462500155340ustar00rootroot00000000000000package main import ( "fmt" ) var CmdStatus = &Cmd{ Name: "status", Desc: "prints some debug status information", Fn: func(env Env, args []string) error { config, err := LoadConfig(env) if err != nil { return err } fmt.Println("direnv exec path", config.SelfPath) fmt.Println("DIRENV_CONFIG", config.ConfDir) fmt.Println("DIRENV_BASH", config.BashPath) loadedRC := config.LoadedRC() foundRC := config.FindRC() if loadedRC != nil { fmt.Println("Loaded RC path", loadedRC.path) fmt.Println("Loaded RC mtime", loadedRC.mtime) fmt.Println("Loaded RC allowed", loadedRC.Allowed()) fmt.Println("Loaded RC allowPath", loadedRC.allowPath) } else { fmt.Println("No .envrc loaded") } if foundRC != nil { fmt.Println("Found RC path", foundRC.path) fmt.Println("Found RC mtime", foundRC.mtime) fmt.Println("Found RC allowed", foundRC.Allowed()) fmt.Println("Found RC allowPath", foundRC.allowPath) } else { fmt.Println("No .envrc found") } return nil }, } direnv-2.7.0/cmd_stdlib.go000066400000000000000000000005351257276462500154650ustar00rootroot00000000000000package main import ( "fmt" ) // `direnv stdlib` var CmdStdlib = &Cmd{ Name: "stdlib", Desc: "Displays the stdlib available in the .envrc execution context", Fn: func(env Env, args []string) (err error) { var config *Config if config, err = LoadConfig(env); err != nil { return } fmt.Printf(STDLIB, config.SelfPath) return }, } direnv-2.7.0/cmd_version.go000066400000000000000000000002611257276462500156650ustar00rootroot00000000000000package main import ( "fmt" ) var CmdVersion = &Cmd{ Name: "version", Private: true, Fn: func(env Env, args []string) error { fmt.Println(VERSION) return nil }, } direnv-2.7.0/commands.go000066400000000000000000000026751257276462500151710ustar00rootroot00000000000000package main import ( "fmt" "strings" "time" ) type Cmd struct { Name string Desc string Args []string Aliases []string NoWait bool Private bool Fn func(env Env, args []string) error } var CmdList []*Cmd func init() { CmdList = []*Cmd{ CmdAllow, CmdApplyDump, CmdDeny, CmdDotEnv, CmdDump, CmdEdit, CmdExec, CmdExpandPath, CmdExport, CmdHelp, CmdHook, CmdReload, CmdStatus, CmdStdlib, CmdVersion, } } func CommandsDispatch(env Env, args []string) error { var command *Cmd var commandName string var commandPrefix string var commandArgs []string if len(args) < 2 { commandName = "help" commandPrefix = args[0] commandArgs = []string{} } else { commandName = args[1] commandPrefix = strings.Join(args[0:2], " ") commandArgs = append([]string{commandPrefix}, args[2:]...) } for _, cmd := range CmdList { if cmd.Name == commandName { command = cmd break } if cmd.Aliases != nil { for _, alias := range cmd.Aliases { if alias == commandName { command = cmd } } } } if command == nil { return fmt.Errorf("Command \"%s\" not found", commandPrefix) } done := make(chan bool, 1) if !command.NoWait { go func() { select { case <-done: return case <-time.After(5 * time.Second): log_error("(%v) is taking a while to execute. Use CTRL-C to give up.", args) } }() } err := command.Fn(env, commandArgs) done <- true return err } direnv-2.7.0/config.go000066400000000000000000000036761257276462500146370ustar00rootroot00000000000000package main import ( "fmt" "os" "os/exec" "path/filepath" "strconv" "strings" ) type Config struct { Env Env WorkDir string // Current directory ConfDir string SelfPath string BashPath string RCDir string } func LoadConfig(env Env) (config *Config, err error) { config = &Config{ Env: env, } config.ConfDir = env[DIRENV_CONFIG] if config.ConfDir == "" { config.ConfDir = XdgConfigDir(env, "direnv") } if config.ConfDir == "" { err = fmt.Errorf("Couldn't find a configuration directory for direnv") return } var exePath string if exePath, err = exec.LookPath(os.Args[0]); err != nil { err = fmt.Errorf("LoadConfig() Lookpath failed: %q", err) return } if exePath, err = filepath.EvalSymlinks(exePath); err != nil { err = fmt.Errorf("LoadConfig() symlink resolution: %q", err) return } exePath = strings.Replace(exePath, "\\", "/", -1) config.SelfPath = exePath config.BashPath = env[DIRENV_BASH] if config.BashPath == "" { if config.BashPath, err = exec.LookPath("bash"); err != nil { err = fmt.Errorf("Can't find bash: %q", err) return } } if config.WorkDir, err = os.Getwd(); err != nil { err = fmt.Errorf("LoadConfig() Getwd failed: %q", err) return } config.RCDir = env[DIRENV_DIR] if len(config.RCDir) > 0 && config.RCDir[0:1] == "-" { config.RCDir = config.RCDir[1:] } return } func (self *Config) AllowDir() string { return filepath.Join(self.ConfDir, "allow") } func (self *Config) LoadedRC() *RC { if self.RCDir == "" { return nil } rcPath := filepath.Join(self.RCDir, ".envrc") mtime, err := strconv.ParseInt(self.Env[DIRENV_MTIME], 10, 64) if err != nil { return nil } return RCFromEnv(rcPath, mtime) } func (self *Config) FindRC() *RC { return FindRC(self.WorkDir, self.AllowDir()) } func (self *Config) EnvDiff() (*EnvDiff, error) { if self.Env[DIRENV_DIFF] == "" { return nil, fmt.Errorf("DIRENV_DIFF is empty") } return LoadEnvDiff(self.Env[DIRENV_DIFF]) } direnv-2.7.0/const.go000066400000000000000000000002631257276462500145050ustar00rootroot00000000000000package main const ( DIRENV_CONFIG = "DIRENV_CONFIG" DIRENV_BASH = "DIRENV_BASH" DIRENV_DIR = "DIRENV_DIR" DIRENV_MTIME = "DIRENV_MTIME" DIRENV_DIFF = "DIRENV_DIFF" ) direnv-2.7.0/docs/000077500000000000000000000000001257276462500137575ustar00rootroot00000000000000direnv-2.7.0/docs/ruby.md000066400000000000000000000066731257276462500152760ustar00rootroot00000000000000# Manage your rubies with direnv and ruby-install direnv is just a shell extension that manages your environment variables depending on the folder you live in. In this article we will explore how it can be used in combination with [ruby-install](https://github.com/postmodern/ruby-install) to manage and select the version of ruby that you want to use in a project. ## The setup First install direnv. This is the quick version on OSX + Bash: ```bash brew install direnv echo 'eval $(direnv hook bash)' >> .bashrc exec $0 ``` Then use [ruby-install](https://github.com/postmodern/ruby-install) to install a couple of ruby versions. We're also creating a couple of aliases for convenience. ``` brew install ruby-install ruby-install ruby 1.9 ruby-install ruby 2.0 cd ~/.rubies ln -s 1.9.3-p448 1.9.3 ln -s 1.9.3-p448 1.9 ln -s 2.0.0-p247 2.0.0 ln -s 2.0.0-p247 2.0 ``` The end goal is that each project will have an ".envrc" file that contains a descriptive syntax like `use ruby 1.9.3` to selects the right ruby version for the project. For that regard we are going to use a couple of commands available in the [direnv stdlib](/stdlib.html) and expand it a bit in the ~/.direnvrc file. Add this to the ~/.direnvrc file (you have to create it if it doesn't exist): ```bash # Usage: use ruby # # Loads the specified ruby version into the environent # use_ruby() { local ruby_dir=$HOME/.rubies/$1 load_prefix $ruby_dir layout ruby } ``` That's it. Now in any project you can run `direnv edit .` and add `use ruby 1.9.3` or `use ruby 2.0` in the file like you want and direnv will select the right ruby version when you enter the project's folder. ## A bit of explanation The last part probably needs a bit more explanation. We make use of a couple of commands that are part of the [stdlib](/stdlib.html) which is availabe in the execution context of an envrc. `use` is a command dispatch that's just there to build the `use something something` dsl so that `use ruby ` will translate into `use_ruby `. `load_prefix` will add a couple of things into the environment, notably add `/bin` into the PATH. This is what makes the specified ruby available. And finally `layout ruby` who like `use` translates into the `layout_ruby` function call. It's used to decribe common project layouts. In the stdlib, the ruby layout will configure rubygems (with the `GEM_HOME` environment variable) to install all the gems into the .direnv/ruby/RUBY_VERSION folder under the project root. This is a bit similar to rvm's gemsets except that they live inside your project's folder. It also configures bundler to install wrapper shims into the .direnv/bin folder which allows you to invoke the commands directly instead of prefixing your ruby programs with `bundle exec` all the time. ## Conclusion As you see this approach is not restricted to ruby. You could have various versions of python installed under ~/.pythons and a `use_python` defined in your ~/.direnvrc. Or perl, php, ... This is the good thing about direnv, it's not restricted to a single language. Actually, wouldn't it be great to have all your project's dependencies available when you enter the project folder ? Not only your ruby version but also the exact redis or mysql or ... version that you want to use, without having to start a VM. I think that's definitely possible using something like the [Nix package manager](http://nixos.org/nix/), something that still needs to be explored in a future post. direnv-2.7.0/docs/screencasts.md000066400000000000000000000014251257276462500166200ustar00rootroot00000000000000# direnv screencasts Here's a couple of screencasts that I recorded a while ago and didn't took the time to share until now. Enjoy ! ## direnv installation on OS X with Homebrew Showing how to install direnv and checking that it works afterwards. ## The direnv security model Explains how the .envrc files are protected from unwanted loading. ## Handling ruby versions with direnv This shows how direnv can be used to build something akin to rvm, rbenv and friends. direnv-2.7.0/docs/sidebar.md000066400000000000000000000010171257276462500157110ustar00rootroot00000000000000# direnv [ditto:searchbar] - [Home]() - [CHANGELOG](#/CHANGELOG) - [LICENSE (MIT)](#/LICENSE) ## Github - [Repo](https://github.com/direnv/direnv) - [Issues](https://github.com/direnv/direnv/issues) - [Releases](https://github.com/direnv/direnv/releases) - [Wiki](https://github.com/direnv/direnv/wiki) ## Guides - [Screencasts](#/docs/screencasts) - [Ruby](#/docs/ruby) - [Vim integration](https://github.com/direnv/direnv.vim) ## Man pages - [man 1 direnv](#man/direnv.1) - [man 1 direnv-stdlib](#man/direnv-stdlib.1) direnv-2.7.0/env.go000066400000000000000000000021521257276462500141460ustar00rootroot00000000000000package main import ( "os" "strings" ) type Env map[string]string // NOTE: We don't support having two variables with the same name. // I've never seen it used in the wild but accoding to POSIX // it's allowed. func GetEnv() Env { env := make(Env) for _, kv := range os.Environ() { kv2 := strings.SplitN(kv, "=", 2) key := kv2[0] value := kv2[1] env[key] = value } return env } func LoadEnv(base64env string) (env Env, err error) { env = make(Env) err = unmarshal(base64env, &env) return } func (env Env) Copy() Env { newEnv := make(Env) for key, value := range env { newEnv[key] = value } return newEnv } func (env Env) ToGoEnv() []string { goEnv := make([]string, len(env)) index := 0 for key, value := range env { goEnv[index] = strings.Join([]string{key, value}, "=") index += 1 } return goEnv } func (env Env) ToShell(shell Shell) string { str := "" for key, value := range env { str += shell.Export(key, value) } return str } func (env Env) Serialize() string { return marshal(env) } func (e1 Env) Diff(e2 Env) *EnvDiff { return BuildEnvDiff(e1, e2) } direnv-2.7.0/env_diff.go000066400000000000000000000041411257276462500151360ustar00rootroot00000000000000package main import ( "strings" ) // A list of keys we don't want to deal with var IGNORED_KEYS = map[string]bool{ // direnv env config "DIRENV_CONFIG": true, "DIRENV_BASH": true, "COMP_WORDBREAKS": true, // Avoids segfaults in bash "PS1": true, // PS1 should not be exported, fixes problem in bash // variables that should change freely "OLDPWD": true, "PWD": true, "SHELL": true, "SHELLOPTS": true, "SHLVL": true, "_": true, } type EnvDiff struct { Prev map[string]string `json:"p"` Next map[string]string `json:"n"` } func BuildEnvDiff(e1, e2 Env) *EnvDiff { diff := &EnvDiff{make(map[string]string), make(map[string]string)} in := func(key string, e Env) bool { _, ok := e[key] return ok } for key := range e1 { if IgnoredEnv(key) { continue } if e2[key] != e1[key] || !in(key, e2) { diff.Prev[key] = e1[key] } } for key := range e2 { if IgnoredEnv(key) { continue } if e2[key] != e1[key] || !in(key, e1) { diff.Next[key] = e2[key] } } return diff } func LoadEnvDiff(base64env string) (diff *EnvDiff, err error) { diff = new(EnvDiff) err = unmarshal(base64env, diff) return } func (self *EnvDiff) Any() bool { return len(self.Prev) > 0 || len(self.Next) > 0 } func (self *EnvDiff) ToShell(shell Shell) string { str := "" for key := range self.Prev { _, ok := self.Next[key] if !ok { str += shell.Unset(key) } } for key, value := range self.Next { str += shell.Export(key, value) } return str } func (self *EnvDiff) Patch(env Env) (newEnv Env) { newEnv = make(Env) for k, v := range env { newEnv[k] = v } for key := range self.Prev { delete(newEnv, key) } for key, value := range self.Next { newEnv[key] = value } return newEnv } func (self *EnvDiff) Reverse() *EnvDiff { return &EnvDiff{self.Next, self.Prev} } func (self *EnvDiff) Serialize() string { return marshal(self) } //// Utils func IgnoredEnv(key string) bool { if strings.HasPrefix(key, "__fish") { return true } if strings.HasPrefix(key, "BASH_FUNC_") { return true } _, found := IGNORED_KEYS[key] return found } direnv-2.7.0/env_diff_test.go000066400000000000000000000020201257276462500161670ustar00rootroot00000000000000package main import ( "reflect" "testing" ) func TestEnvDiff(t *testing.T) { diff := &EnvDiff{map[string]string{"FOO": "bar"}, map[string]string{"BAR": "baz"}} out := diff.Serialize() diff2, err := LoadEnvDiff(out) if err != nil { t.Errorf("parse error", err) } if len(diff2.Prev) != 1 { t.Errorf("len(diff2.prev) != 1", len(diff2.Prev)) } if len(diff2.Next) != 1 { t.Errorf("len(diff2.next) != 0", len(diff2.Next)) } } // Issue #114 // Check that empty environment variables correctly appear in the diff func TestEnvDiffEmptyValue(t *testing.T) { before := Env{} after := Env{"FOO": ""} diff := BuildEnvDiff(before, after) if !reflect.DeepEqual(diff.Next, map[string]string(after)) { t.Errorf("diff.Next != after (%#+v != %#+v)", diff.Next, after) } } func TestIgnoredEnv(t *testing.T) { if !IgnoredEnv(DIRENV_BASH) { t.Fail() } if IgnoredEnv(DIRENV_DIFF) { t.Fail() } if !IgnoredEnv("_") { t.Fail() } if !IgnoredEnv("__fish_foo") { t.Fail() } if !IgnoredEnv("__fishx") { t.Fail() } } direnv-2.7.0/env_test.go000066400000000000000000000005061257276462500152060ustar00rootroot00000000000000package main import ( "testing" ) func TestEnv(t *testing.T) { env := Env{"FOO": "bar"} out := env.Serialize() env2, err := LoadEnv(out) if err != nil { t.Errorf("parse error", err) } if env2["FOO"] != "bar" { t.Errorf("FOO != bar", env2["FOO"]) } if len(env2) != 1 { t.Errorf("len != 1", len(env2)) } } direnv-2.7.0/index.html000066400000000000000000000027001257276462500150230ustar00rootroot00000000000000 direnv - unclutter your .profile
back to top
edit
Loading ...
direnv-2.7.0/log.go000066400000000000000000000010141257276462500141330ustar00rootroot00000000000000package main import ( "fmt" "os" ) const ( defaultLogFormat = "direnv: %s" errorLogFormat = "\033[31mdirenv: %s\033[0m" ) func log_error(msg string, a ...interface{}) { log(errorLogFormat, msg, a...) } func log_status(env Env, msg string, a ...interface{}) { format, ok := env["DIRENV_LOG_FORMAT"] if !ok { format = defaultLogFormat } if format != "" { log(format, msg, a...) } } func log(format, msg string, a ...interface{}) { msg = fmt.Sprintf(format+"\n", msg) fmt.Fprintf(os.Stderr, msg, a...) } direnv-2.7.0/look_path.go000066400000000000000000000017001257276462500153340ustar00rootroot00000000000000package main import ( "errors" "os" "strings" ) // Similar to os/exec.LookPath except we pass in the PATH func lookPath(file string, pathenv string) (string, error) { if strings.Contains(file, "/") { err := findExecutable(file) if err == nil { return file, nil } return "", err } if pathenv == "" { return "", errNotFound } for _, dir := range strings.Split(pathenv, ":") { if dir == "" { // Unix shell semantics: path element "" means "." dir = "." } path := dir + "/" + file if err := findExecutable(path); err == nil { return path, nil } } return "", errNotFound } // ErrNotFound is the error resulting if a path search failed to find an executable file. var errNotFound = errors.New("executable file not found in $PATH") func findExecutable(file string) error { d, err := os.Stat(file) if err != nil { return err } if m := d.Mode(); !m.IsDir() && m&0111 != 0 { return nil } return os.ErrPermission } direnv-2.7.0/main.go000066400000000000000000000002731257276462500143040ustar00rootroot00000000000000package main import ( "os" ) func main() { var env = GetEnv() var args = os.Args err := CommandsDispatch(env, args) if err != nil { log_error("error %v", err) os.Exit(1) } } direnv-2.7.0/man/000077500000000000000000000000001257276462500136025ustar00rootroot00000000000000direnv-2.7.0/man/direnv-stdlib.1000066400000000000000000000127131257276462500164360ustar00rootroot00000000000000.TH DIRENV\-STDLIB 1 "APRIL 2014" direnv "User Manuals" .SH NAME .PP direnv_stdlib \- the ".envrc" stdlib .SH SYNOPSIS .PP \fB\fCdirenv stdlib\fR .SH DESCRIPTION .PP Outputs a bash script called the \fIstdlib\fP\&. The following commands are included in that script and loaded in the context of an ".envrc". In addition, it also loads the file in "~/.direnvrc" if it exists. .SH STDLIB .RS .IP \(bu 2 \fB\fChas\fR \fIcommand\fP: Returns 0 if the \fIcommand\fP is available. Returns 1 otherwise. It can be a binary in the PATH or a shell function. .RE .PP Example: .PP .RS .nf if has curl; then echo "Yes we do" fi .fi .RE .RS .IP \(bu 2 \fB\fCexpand_path\fR \fIrel_path\fP [\fIrelative_to\fP]: Outputs the absolute path of \fIrel_path\fP relative to \fIrelative_to\fP or the current directory. .RE .PP Example: .PP .RS .nf cd /usr/local/games expand_path ../foo # output: /usr/local/foo .fi .RE .RS .IP \(bu 2 \fB\fCdotenv\fR [\fIdotenv_path\fP]: Loads a ".env" file into the current environment .IP \(bu 2 \fB\fCuser_rel_path\fR \fIabs_path\fP: Transforms an absolute path \fIabs_path\fP into a user\-relative path if possible. .RE .PP Example: .PP .RS .nf echo $HOME # output: /home/user user_rel_path /home/user/my/project # output: ~/my/project user_rel_path /usr/local/lib # output: /usr/local/lib .fi .RE .RS .IP \(bu 2 \fB\fCfind_up\fR \fIfilename\fP: Outputs the path of \fIfilename\fP when searched from the current directory up to /. Returns 1 if the file has not been found. .RE .PP Example: .PP .RS .nf cd /usr/local/my mkdir \-p project/foo touch bar cd project/foo find_up bar # output: /usr/local/my/bar .fi .RE .RS .IP \(bu 2 \fB\fCsource_env\fR \fIfile\fIor\fPdir_path\fP: Loads another ".envrc" either by specifying its path or filename. .IP \(bu 2 \fB\fCsource_up\fR [\fIfilename\fP]: Loads another ".envrc" if found with the \fB\fCfind_up\fR command. .IP \(bu 2 \fB\fCdirenv_load\fR [\fIcommand\-generating\-dump\-output\fP] Applies the environment generated by running \fIargv\fP as a command. This is useful for adopting the environment of a child process \- cause that process to run "direnv dump" and then wrap the results with direnv_load. .RE .PP Example: .PP .RS .nf direnv_load opam\-env exec \-\- direnv dump .fi .RE .RS .IP \(bu 2 \fB\fCPATH_add\fR \fIpath\fP: Prepends the expanded \fIpath\fP to the PATH environment variable. It prevents a common mistake where PATH is replaced by only the new \fIpath\fP\&. .RE .PP Example: .PP .RS .nf pwd # output: /home/user/my/project PATH_add bin echo $PATH # output: /home/user/my/project/bin:/usr/bin:/bin .fi .RE .RS .IP \(bu 2 \fB\fCpath_add\fR \fIvarname\fP \fIpath\fP: Works like \fB\fCPATH_add\fR except that it's for an arbitrary \fIvarname\fP\&. .IP \(bu 2 \fB\fCload_prefix\fR \fIprefix_path\fP: Expands some common path variables for the given \fIprefix_path\fP prefix. This is useful if you installed something in the \fIprefix_path\fP using \fB\fC\&./configure \-\-prefix=$prefix_path && make install\fR and want to use it in the project. .RE .PP Variables set: .PP .RS .nf CPATH LD_LIBRARY_PATH LIBRARY_PATH MANPATH PATH PKG_CONFIG_PATH .fi .RE .PP Example: .PP .RS .nf \&./configure \-\-prefix=$HOME/rubies/ruby\-1.9.3 make && make install # Then in the .envrc load_prefix ~/rubies/ruby\-1.9.3 .fi .RE .RS .IP \(bu 2 \fB\fClayout\fR \fItype\fP: A semantic dispatch used to describe common project layouts. .IP \(bu 2 \fB\fClayout go\fR: Sets the GOPATH environment variable to the current directory. .IP \(bu 2 \fB\fClayout node\fR: Adds "$PWD/node_modules/.bin" to the PATH environment variable. .IP \(bu 2 \fB\fClayout perl\fR: Setup environment variables required by perl's local::lib See \[la]http://search.cpan.org/dist/local-lib/lib/local/lib.pm\[ra] for more details .IP \(bu 2 \fB\fClayout python\fR [\fIpython_exe\fP]: Creates and loads a virtualenv environment under \fB\fC$PWD/.direnv/python\-$python_version\fR\&. This forces the installation of any egg into the project's sub\-folder. .PP It's possible to specify the python executable if you want to use different versions of python (eg: \fB\fClayout python python3\fR). .PP Note that previously virtualenv was located under \fB\fC$PWD/.direnv/virtualenv\fR and will be re\-used by direnv if it exists. .IP \(bu 2 \fB\fClayout python3\fR: A shortcut for \fB\fClayout python python3\fR .IP \(bu 2 \fB\fClayout ruby\fR: Sets the GEM\fIHOME environment variable to `$PWD/.direnv/ruby/RUBY\fPVERSION\fB\fC\&. This forces the installation of any gems into the project's sub\-folder. If you're using bundler it will create wrapper programs that can be invoked directly instead of using the\fRbundle exec` prefix. .IP \(bu 2 \fB\fCuse\fR \fIprogram_name\fP [\fIversion\fP]: A semantic command dispatch intended for loading external dependencies into the environment. .RE .PP Example: .PP .RS .nf use_ruby() { echo "Ruby $1" } use ruby 1.9.3 # output: Ruby 1.9.3 .fi .RE .RS .IP \(bu 2 \fB\fCuse rbenv\fR: Loads rbenv which add the ruby wrappers available on the PATH. .IP \(bu 2 \fB\fCuse nix [...]\fR: Load environment variables from \fB\fCnix\-shell\fR\&. .PP If you have a \fB\fCdefault.nix\fR or \fB\fCshell.nix\fR these will be used by default, but you can also specify packages directly (e.g \fB\fCuse nix \-p ocaml\fR). .PP See \[la]http://nixos.org/nix/manual/#sec-nix-shell\[ra] .IP \(bu 2 \fB\fCrvm\fR ...: Should work just like in the shell if you have rvm installed. .RE .SH COPYRIGHT .PP Copyright (C) 2014 zimbatm \[la]http://zimbatm.com\[ra] and contributors under the MIT licence. .SH SEE ALSO .PP .BR direnv (1) direnv-2.7.0/man/direnv-stdlib.1.md000066400000000000000000000116621257276462500170370ustar00rootroot00000000000000DIRENV-STDLIB 1 "APRIL 2014" direnv "User Manuals" ================================================== NAME ---- direnv_stdlib - the ".envrc" stdlib SYNOPSIS -------- `direnv stdlib` DESCRIPTION ----------- Outputs a bash script called the *stdlib*. The following commands are included in that script and loaded in the context of an ".envrc". In addition, it also loads the file in "~/.direnvrc" if it exists. STDLIB ------ * `has` *command*: Returns 0 if the *command* is available. Returns 1 otherwise. It can be a binary in the PATH or a shell function. Example: if has curl; then echo "Yes we do" fi * `expand_path` *rel_path* [*relative_to*]: Outputs the absolute path of *rel_path* relative to *relative_to* or the current directory. Example: cd /usr/local/games expand_path ../foo # output: /usr/local/foo * `dotenv` [*dotenv_path*]: Loads a ".env" file into the current environment * `user_rel_path` *abs_path*: Transforms an absolute path *abs_path* into a user-relative path if possible. Example: echo $HOME # output: /home/user user_rel_path /home/user/my/project # output: ~/my/project user_rel_path /usr/local/lib # output: /usr/local/lib * `find_up` *filename*: Outputs the path of *filename* when searched from the current directory up to /. Returns 1 if the file has not been found. Example: cd /usr/local/my mkdir -p project/foo touch bar cd project/foo find_up bar # output: /usr/local/my/bar * `source_env` *file_or_dir_path*: Loads another ".envrc" either by specifying its path or filename. * `source_up` [*filename*]: Loads another ".envrc" if found with the `find_up` command. * `direnv_load` [*command-generating-dump-output*] Applies the environment generated by running *argv* as a command. This is useful for adopting the environment of a child process - cause that process to run "direnv dump" and then wrap the results with direnv_load. Example: direnv_load opam-env exec -- direnv dump * `PATH_add` *path*: Prepends the expanded *path* to the PATH environment variable. It prevents a common mistake where PATH is replaced by only the new *path*. Example: pwd # output: /home/user/my/project PATH_add bin echo $PATH # output: /home/user/my/project/bin:/usr/bin:/bin * `path_add` *varname* *path*: Works like `PATH_add` except that it's for an arbitrary *varname*. * `load_prefix` *prefix_path*: Expands some common path variables for the given *prefix_path* prefix. This is useful if you installed something in the *prefix_path* using `./configure --prefix=$prefix_path && make install` and want to use it in the project. Variables set: CPATH LD_LIBRARY_PATH LIBRARY_PATH MANPATH PATH PKG_CONFIG_PATH Example: ./configure --prefix=$HOME/rubies/ruby-1.9.3 make && make install # Then in the .envrc load_prefix ~/rubies/ruby-1.9.3 * `layout` *type*: A semantic dispatch used to describe common project layouts. * `layout go`: Sets the GOPATH environment variable to the current directory. * `layout node`: Adds "$PWD/node_modules/.bin" to the PATH environment variable. * `layout perl`: Setup environment variables required by perl's local::lib See http://search.cpan.org/dist/local-lib/lib/local/lib.pm for more details * `layout python` [*python_exe*]: Creates and loads a virtualenv environment under `$PWD/.direnv/python-$python_version`. This forces the installation of any egg into the project's sub-folder. It's possible to specify the python executable if you want to use different versions of python (eg: `layout python python3`). Note that previously virtualenv was located under `$PWD/.direnv/virtualenv` and will be re-used by direnv if it exists. * `layout python3`: A shortcut for `layout python python3` * `layout ruby`: Sets the GEM_HOME environment variable to `$PWD/.direnv/ruby/RUBY_VERSION`. This forces the installation of any gems into the project's sub-folder. If you're using bundler it will create wrapper programs that can be invoked directly instead of using the `bundle exec` prefix. * `use` *program_name* [*version*]: A semantic command dispatch intended for loading external dependencies into the environment. Example: use_ruby() { echo "Ruby $1" } use ruby 1.9.3 # output: Ruby 1.9.3 * `use rbenv`: Loads rbenv which add the ruby wrappers available on the PATH. * `use nix [...]`: Load environment variables from `nix-shell`. If you have a `default.nix` or `shell.nix` these will be used by default, but you can also specify packages directly (e.g `use nix -p ocaml`). See http://nixos.org/nix/manual/#sec-nix-shell * `rvm` ...: Should work just like in the shell if you have rvm installed. COPYRIGHT --------- Copyright (C) 2014 zimbatm and contributors under the MIT licence. SEE ALSO -------- direnv(1) direnv-2.7.0/man/direnv.1000066400000000000000000000073251257276462500151620ustar00rootroot00000000000000.TH DIRENV 1 "APRIL 2014" direnv "User Manuals" .SH NAME .PP direnv \- unclutter your .profile .SH SYNOPSIS .PP \fB\fCdirenv\fR \fIcommand\fP ... .SH DESCRIPTION .PP \fB\fCdirenv\fR is an environment variable manager for your shell. It knows how to hook into bash, zsh and fish shell to load or unload environment variables depending on your current directory. This allows to have project\-specific environment variables and not clutter the "~/.profile" file. .PP Before each prompt it checks for the existence of an ".envrc" file in the current and parent directories. If the file exists, it is loaded into a bash sub\-shell and all exported variables are then captured by direnv and then made available to your current shell. .PP Because direnv is compiled into a single static executable it is fast enough to be unnoticeable on each prompt. It is also language agnostic and can be used to build solutions similar to rbenv, pyenv, phpenv, ... .SH EXAMPLE .PP .RS .nf $ cd ~/my_project $ echo ${FOO\-nope} nope $ echo export FOO=foo > .envrc \&.envrc is not allowed $ direnv allow . direnv: reloading direnv: loading .envrc direnv export: +FOO $ echo ${FOO\-nope} foo $ cd .. direnv: unloading direnv export: ~PATH $ echo ${FOO\-nope} nope .fi .RE .SH SETUP .PP For direnv to work properly it needs to be hooked into the shell. Each shell has it's own extension mechanism: .SS BASH .PP Add the following line at the end of your "~/.bashrc" file: .PP \fB\fCeval "$(direnv hook bash)"\fR .PP Make sure it appears even after rvm, git\-prompt and other shell extensions that manipulate your prompt. .SS ZSH .PP Add the following line at the end of your "~/.zshrc" file: .PP \fB\fCeval "$(direnv hook zsh)"\fR .SS FISH .PP Add the following line at the end of your "~/.config/fish/config.fish" file: .PP \fB\fCeval (direnv hook fish)\fR .SS TCSH .PP Add the following line at the end of your "~/.cshrc" file: .PP .RS .nf eval `direnv hook tcsh` .fi .RE .SH USAGE .PP In some target folder, create an ".envrc" file and add some .BR export (1) directives in it. .PP On the next prompt you will notice that direnv complains about the ".envrc" being blocked. This is the security mechanism to avoid loading new files automatically. Otherwise any git repo that you pull, or tar archive that you unpack, would be able to wipe your hard drive once you \fB\fCcd\fR into it. .PP So here we are pretty sure that it won't do anything bad. Type \fB\fCdirenv allow .\fR and watch direnv loading your new environment. Note that \fB\fCdirenv edit .\fR is a handy shortcut that open the file in your $EDITOR and automatically allows it if the file's modification time has changed. .PP Now that the environment is loaded you can notice that once you \fB\fCcd\fR out of the directory it automatically gets unloaded. If you \fB\fCcd\fR back into it it's loaded again. That's the base of the mechanism that allows you to build cool things. .PP Exporting variables by hand is a bit repetitive so direnv provides a set of utility functions that are made available in the context of the ".envrc" file. Check the .BR direnv-stdlib (1) man page for more details. You can also define your own extensions inside a "~/.direnvrc" file. .PP Hopefully this is enough to get you started. .SH CONTRIBUTE .PP Bug reports, contributions and forks are welcome. .PP All bugs or other forms of discussion happen on \[la]http://github.com/direnv/direnv/issues\[ra] .PP There is also a wiki available where you can share your usage patterns or other tips and tricks \[la]https://github.com/direnv/direnv/wiki\[ra] .PP Or drop by on the #direnv channel on FreeNode \[la]irc://#direnv@FreeNode\[ra] to have a chat. .SH COPYRIGHT .PP Copyright (C) 2014 zimbatm and contributors under the MIT licence. .SH SEE ALSO .PP .BR direnv-stdlib (1) direnv-2.7.0/man/direnv.1.md000066400000000000000000000071171257276462500155600ustar00rootroot00000000000000DIRENV 1 "APRIL 2014" direnv "User Manuals" =========================================== NAME ---- direnv - unclutter your .profile SYNOPSIS -------- `direnv` *command* ... DESCRIPTION ----------- `direnv` is an environment variable manager for your shell. It knows how to hook into bash, zsh and fish shell to load or unload environment variables depending on your current directory. This allows to have project-specific environment variables and not clutter the "~/.profile" file. Before each prompt it checks for the existence of an ".envrc" file in the current and parent directories. If the file exists, it is loaded into a bash sub-shell and all exported variables are then captured by direnv and then made available to your current shell. Because direnv is compiled into a single static executable it is fast enough to be unnoticeable on each prompt. It is also language agnostic and can be used to build solutions similar to rbenv, pyenv, phpenv, ... EXAMPLE ------- ``` $ cd ~/my_project $ echo ${FOO-nope} nope $ echo export FOO=foo > .envrc .envrc is not allowed $ direnv allow . direnv: reloading direnv: loading .envrc direnv export: +FOO $ echo ${FOO-nope} foo $ cd .. direnv: unloading direnv export: ~PATH $ echo ${FOO-nope} nope ``` SETUP ----- For direnv to work properly it needs to be hooked into the shell. Each shell has it's own extension mechanism: ### BASH Add the following line at the end of your "~/.bashrc" file: `eval "$(direnv hook bash)"` Make sure it appears even after rvm, git-prompt and other shell extensions that manipulate your prompt. ### ZSH Add the following line at the end of your "~/.zshrc" file: `eval "$(direnv hook zsh)"` ### FISH Add the following line at the end of your "~/.config/fish/config.fish" file: `eval (direnv hook fish)` ### TCSH Add the following line at the end of your "~/.cshrc" file: eval `direnv hook tcsh` USAGE ----- In some target folder, create an ".envrc" file and add some export(1) directives in it. On the next prompt you will notice that direnv complains about the ".envrc" being blocked. This is the security mechanism to avoid loading new files automatically. Otherwise any git repo that you pull, or tar archive that you unpack, would be able to wipe your hard drive once you `cd` into it. So here we are pretty sure that it won't do anything bad. Type `direnv allow .` and watch direnv loading your new environment. Note that `direnv edit .` is a handy shortcut that open the file in your $EDITOR and automatically allows it if the file's modification time has changed. Now that the environment is loaded you can notice that once you `cd` out of the directory it automatically gets unloaded. If you `cd` back into it it's loaded again. That's the base of the mechanism that allows you to build cool things. Exporting variables by hand is a bit repetitive so direnv provides a set of utility functions that are made available in the context of the ".envrc" file. Check the direnv-stdlib(1) man page for more details. You can also define your own extensions inside a "~/.direnvrc" file. Hopefully this is enough to get you started. CONTRIBUTE ---------- Bug reports, contributions and forks are welcome. All bugs or other forms of discussion happen on There is also a wiki available where you can share your usage patterns or other tips and tricks Or drop by on the [#direnv channel on FreeNode](irc://#direnv@FreeNode) to have a chat. COPYRIGHT --------- Copyright (C) 2014 zimbatm and contributors under the MIT licence. SEE ALSO -------- direnv-stdlib(1) direnv-2.7.0/marshal.go000066400000000000000000000021501257276462500150030ustar00rootroot00000000000000package main import ( "bytes" "compress/zlib" "encoding/base64" "encoding/json" "fmt" "io" "strings" ) func marshal(obj interface{}) string { jsonData, err := json.Marshal(obj) if err != nil { panic(fmt.Errorf("marshal(): %v", err)) } zlibData := bytes.NewBuffer([]byte{}) w := zlib.NewWriter(zlibData) w.Write(jsonData) w.Close() base64Data := base64.URLEncoding.EncodeToString(zlibData.Bytes()) return base64Data } func unmarshal(base64env string, obj interface{}) error { base64env = strings.TrimSpace(base64env) data, err := base64.URLEncoding.DecodeString(base64env) if err != nil { return fmt.Errorf("unmarshal() base64 decoding: %v", err) } zlibReader := bytes.NewReader(data) w, err := zlib.NewReader(zlibReader) if err != nil { return fmt.Errorf("unmarshal() zlib opening: %v", err) } envData := bytes.NewBuffer([]byte{}) _, err = io.Copy(envData, w) if err != nil { return fmt.Errorf("unmarshal() zlib decoding: %v", err) } w.Close() err = json.Unmarshal(envData.Bytes(), &obj) if err != nil { return fmt.Errorf("unmarshal() json parsing: %v", err) } return nil } direnv-2.7.0/rc.go000066400000000000000000000077751257276462500140020ustar00rootroot00000000000000package main import ( "crypto/sha256" "fmt" "io" "os" "os/exec" "path/filepath" "strings" "time" ) type RC struct { path string mtime int64 allowPath string } func FindRC(wd string, allowDir string) *RC { rcPath := findUp(wd, ".envrc") if rcPath == "" { return nil } return RCFromPath(rcPath, allowDir) } func RCFromPath(path string, allowDir string) *RC { mtime, err := fileMtime(path) if err != nil { return nil } hash, err := fileHash(path) if err != nil { return nil } allowPath := filepath.Join(allowDir, hash) allowMtime, _ := fileMtime(allowPath) if allowMtime > mtime { mtime = allowMtime } return &RC{path, mtime, allowPath} } func RCFromEnv(path string, mtime int64) *RC { return &RC{path, mtime, ""} } func (self *RC) Allow() (err error) { if self.allowPath == "" { return fmt.Errorf("Cannot allow empty path") } if err = os.MkdirAll(filepath.Dir(self.allowPath), 0755); err != nil { return } if err = touch(self.allowPath); err != nil { return } self.mtime, err = fileMtime(self.allowPath) return } func (self *RC) Deny() error { return os.Remove(self.allowPath) } func (self *RC) Allowed() bool { _, err := os.Stat(self.allowPath) return err == nil } // Makes the path relative to the current directory. Except when both paths // are completely different. // Eg: /home/foo and /home/bar => ../foo // But: /home/foo and /tmp/bar => /home/foo func (self *RC) RelTo(wd string) string { if rootDir(wd) != rootDir(self.path) { return self.path } x, err := filepath.Rel(wd, self.path) if err != nil { panic(err) } return x } func (self *RC) Touch() error { return touch(self.path) } const NOT_ALLOWED = "%s is blocked. Run `direnv allow` to approve its content." func (self *RC) Load(config *Config, env Env) (newEnv Env, err error) { wd := config.WorkDir direnv := config.SelfPath if !self.Allowed() { return nil, fmt.Errorf(NOT_ALLOWED, self.RelTo(wd)) } argtmpl := `eval "$("%s" stdlib)" >&2 && source_env "%s" >&2 && "%s" dump` arg := fmt.Sprintf(argtmpl, direnv, self.RelTo(wd), direnv) cmd := exec.Command(config.BashPath, "--noprofile", "--norc", "-c", arg) cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr cmd.Env = env.ToGoEnv() cmd.Dir = wd out, err := cmd.Output() if err != nil { return } newEnv, err = LoadEnv(string(out)) if err != nil { return } // Save state newEnv[DIRENV_DIR] = "-" + filepath.Dir(self.path) newEnv[DIRENV_MTIME] = fmt.Sprintf("%d", self.mtime) newEnv[DIRENV_DIFF] = env.Diff(newEnv).Serialize() return } /// Utils func rootDir(path string) string { path, err := filepath.Abs(path) if err != nil { panic(err) } i := strings.Index(path[1:], "/") if i < 0 { return path } return path[:i+1] } func eachDir(path string) (paths []string) { path, err := filepath.Abs(path) if err != nil { return } paths = []string{path} if path == "/" { return } for i := len(path) - 1; i >= 0; i-- { if path[i] == os.PathSeparator { path = path[:i] if path == "" { path = "/" } paths = append(paths, path) } } return } func fileExists(path string) bool { _, err := os.Stat(path) return err == nil } func fileMtime(path string) (int64, error) { fileInfo, err := os.Stat(path) if err != nil { return 0, err } return fileInfo.ModTime().Unix(), nil } func fileHash(path string) (hash string, err error) { if path, err = filepath.Abs(path); err != nil { return } fd, err := os.Open(path) if err != nil { return } hasher := sha256.New() hasher.Write([]byte(path + "\n")) if _, err = io.Copy(hasher, fd); err != nil { return } return fmt.Sprintf("%x", hasher.Sum(nil)), nil } // Creates a file func touch(path string) (err error) { file, err := os.OpenFile(path, os.O_CREATE, 0644) if err != nil { return } file.Close() t := time.Now() return os.Chtimes(path, t, t) } func findUp(searchDir string, fileName string) (path string) { for _, dir := range eachDir(searchDir) { path = filepath.Join(dir, fileName) if fileExists(path) { return } } return "" } direnv-2.7.0/rc_test.go000066400000000000000000000006021257276462500150170ustar00rootroot00000000000000package main import ( "testing" ) func TestSomething(t *testing.T) { paths := eachDir("/foo/b//bar/") if len(paths) != 4 { t.Fail() } paths = eachDir("/") if len(paths) != 1 && paths[0] != "/" { t.Fail() } } func TestRootDir(t *testing.T) { var r string r = rootDir("/foo") if r != "/foo" { t.Error(r) } r = rootDir("/foo/bar") if r != "/foo" { t.Error(r) } } direnv-2.7.0/script/000077500000000000000000000000001257276462500143335ustar00rootroot00000000000000direnv-2.7.0/script/str2go000077500000000000000000000010301257276462500154730ustar00rootroot00000000000000#!/usr/bin/env ruby # # str2go # package_name = ARGV[0] constant_name = ARGV[1] input = $stdin output = $stdout def line_to_go(line) '"' + line.gsub('\\', '\\\\\\').gsub("\n", '\n').gsub('"', '\"').gsub(/[^[:ascii:]]/) do |char| "\\#{char.ord}" end + '"' end def lines_to_go(lines) lines.map do |line| "\t#{line_to_go(line)}" end.join(" +\n").sub("\t", '') end output.puts "package #{package_name}" output.puts output.puts "const #{constant_name} = #{lines_to_go(input.each_line)}" direnv-2.7.0/shell.go000066400000000000000000000052431257276462500144710ustar00rootroot00000000000000package main import ( "fmt" "path/filepath" ) /* * Shells */ type Shell interface { Hook() string Export(key, value string) string Unset(key string) string } func DetectShell(target string) Shell { target = filepath.Base(target) // $0 starts with "-" if target[0:1] == "-" { target = target[1:] } switch target { case "bash": return BASH case "zsh": return ZSH case "fish": return FISH case "vim": return VIM case "tcsh": return TCSH } return nil } /* * Escaping */ const ( ACK = 6 TAB = 9 LF = 10 CR = 13 US = 31 SPACE = 32 AMPERSTAND = 38 SINGLE_QUOTE = 39 PLUS = 43 NINE = 57 QUESTION = 63 LOWERCASE_Z = 90 OPEN_BRACKET = 91 BACKSLASH = 92 UNDERSCORE = 95 CLOSE_BRACKET = 93 BACKTICK = 96 TILDA = 126 DEL = 127 ) // https://github.com/solidsnack/shell-escape/blob/master/Text/ShellEscape/Bash.hs /* A Bash escaped string. The strings are wrapped in @$\'...\'@ if any bytes within them must be escaped; otherwise, they are left as is. Newlines and other control characters are represented as ANSI escape sequences. High bytes are represented as hex codes. Thus Bash escaped strings will always fit on one line and never contain non-ASCII bytes. */ func ShellEscape(str string) string { if str == "" { return "''" } in := []byte(str) out := "" i := 0 l := len(in) escape := false hex := func(char byte) { escape = true out += fmt.Sprintf("\\x%02x", char) } backslash := func(char byte) { escape = true out += string([]byte{BACKSLASH, char}) } escaped := func(str string) { escape = true out += str } quoted := func(char byte) { escape = true out += string([]byte{char}) } literal := func(char byte) { out += string([]byte{char}) } for i < l { char := in[i] switch { case char == TAB: escaped(`\t`) case char == LF: escaped(`\n`) case char == CR: escaped(`\r`) case char <= US: hex(char) case char <= AMPERSTAND: quoted(char) case char == SINGLE_QUOTE: backslash(char) case char <= PLUS: quoted(char) case char <= NINE: literal(char) case char <= QUESTION: quoted(char) case char <= LOWERCASE_Z: literal(char) case char == OPEN_BRACKET: quoted(char) case char == BACKSLASH: backslash(char) case char <= CLOSE_BRACKET: quoted(char) case char == UNDERSCORE: literal(char) case char <= BACKTICK: quoted(char) case char <= LOWERCASE_Z: literal(char) case char <= TILDA: quoted(char) case char == DEL: hex(char) default: hex(char) } i += 1 } if escape { out = "$'" + out + "'" } return out } direnv-2.7.0/shell_bash.go000066400000000000000000000010351257276462500154610ustar00rootroot00000000000000package main type bash int var BASH bash const BASH_HOOK = ` _direnv_hook() { eval "$(direnv export bash)"; }; if ! [[ "$PROMPT_COMMAND" =~ _direnv_hook ]]; then PROMPT_COMMAND="_direnv_hook;$PROMPT_COMMAND"; fi ` func (b bash) Hook() string { return BASH_HOOK } func (b bash) Escape(str string) string { return ShellEscape(str) } func (b bash) Export(key, value string) string { return "export " + b.Escape(key) + "=" + b.Escape(value) + ";" } func (b bash) Unset(key string) string { return "unset " + b.Escape(key) + ";" } direnv-2.7.0/shell_fish.go000066400000000000000000000025161257276462500155020ustar00rootroot00000000000000package main import ( "fmt" "strings" ) type fish int var FISH fish func (f fish) Hook() string { return ` function __direnv_export_eval --on-event fish_prompt; eval (direnv export fish); end ` } func (f fish) Escape(str string) string { in := []byte(str) out := "'" i := 0 l := len(in) hex := func(char byte) { out += fmt.Sprintf("'\\x%02x'", char) } backslash := func(char byte) { out += string([]byte{BACKSLASH, char}) } escaped := func(str string) { out += "'" + str + "'" } literal := func(char byte) { out += string([]byte{char}) } for i < l { char := in[i] switch { case char == TAB: escaped(`\t`) case char == LF: escaped(`\n`) case char == CR: escaped(`\r`) case char <= US: hex(char) case char == SINGLE_QUOTE: backslash(char) case char == BACKSLASH: backslash(char) case char <= TILDA: literal(char) case char == DEL: hex(char) default: hex(char) } i += 1 } out += "'" return out } func (f fish) Export(key, value string) string { if key == "PATH" { command := "set -x -g PATH" for _, path := range strings.Split(value, ":") { command += " " + f.Escape(path) } return command + ";" } return "set -x -g " + f.Escape(key) + " " + f.Escape(value) + ";" } func (f fish) Unset(key string) string { return "set -e -g " + f.Escape(key) + ";" } direnv-2.7.0/shell_tcsh.go000066400000000000000000000034611257276462500155120ustar00rootroot00000000000000package main import ( "fmt" "strings" ) type tcsh int var TCSH tcsh func (f tcsh) Hook() string { return "alias precmd 'eval `direnv export tcsh`' " } func (f tcsh) Escape(str string) string { if str == "" { return "''" } in := []byte(str) out := "" i := 0 l := len(in) hex := func(char byte) { out += fmt.Sprintf("\\x%02x", char) } backslash := func(char byte) { out += string([]byte{BACKSLASH, char}) } escaped := func(str string) { out += str } quoted := func(char byte) { out += string([]byte{char}) } literal := func(char byte) { out += string([]byte{char}) } for i < l { char := in[i] switch { case char == TAB: escaped(`\t`) case char == LF: escaped(`\n`) case char == CR: escaped(`\r`) case char == SPACE: backslash(char) case char <= US: hex(char) case char <= AMPERSTAND: quoted(char) case char == SINGLE_QUOTE: backslash(char) case char <= PLUS: quoted(char) case char <= NINE: literal(char) case char <= QUESTION: quoted(char) case char <= LOWERCASE_Z: literal(char) case char == OPEN_BRACKET: quoted(char) case char == BACKSLASH: backslash(char) case char <= CLOSE_BRACKET: quoted(char) case char == UNDERSCORE: literal(char) case char <= BACKTICK: quoted(char) case char <= LOWERCASE_Z: literal(char) case char <= TILDA: quoted(char) case char == DEL: hex(char) default: hex(char) } i += 1 } return out } func (f tcsh) Export(key, value string) string { if key == "PATH" { command := "set path = (" for _, path := range strings.Split(value, ":") { command += " " + f.Escape(path) } return command + " );" } return "setenv " + f.Escape(key) + " " + f.Escape(value) + ";" } func (f tcsh) Unset(key string) string { return "unsetenv " + f.Escape(key) + ";" } direnv-2.7.0/shell_test.go000066400000000000000000000015571257276462500155340ustar00rootroot00000000000000package main import ( "testing" ) func TestShellEscape(t *testing.T) { assertEqual(t, `''`, ShellEscape("")) assertEqual(t, `$'escape\'quote'`, ShellEscape("escape'quote")) assertEqual(t, `$'foo\r\n\tbar'`, ShellEscape("foo\r\n\tbar")) assertEqual(t, `$'foo bar'`, ShellEscape("foo bar")) assertEqual(t, `$'\xc3\xa9'`, ShellEscape("é")) } func TestShellDetection(t *testing.T) { assertNotNil(t, DetectShell("-bash")) assertNotNil(t, DetectShell("-/bin/bash")) assertNotNil(t, DetectShell("-/usr/local/bin/bash")) assertNotNil(t, DetectShell("-zsh")) assertNotNil(t, DetectShell("-/bin/zsh")) assertNotNil(t, DetectShell("-/usr/local/bin/zsh")) } func assertNotNil(t *testing.T, a Shell) { if a == nil { t.Error("Expected not to be nil") } } func assertEqual(t *testing.T, a, b string) { if a != b { t.Errorf("Expected \"%v\" to equal \"%v\"", b, a) } } direnv-2.7.0/shell_vim.go000066400000000000000000000012451257276462500153420ustar00rootroot00000000000000package main import ( "os" "strings" ) type vim int var VIM vim func (x vim) Hook() string { log_error("this feature is not supported. Install the direnv.vim plugin instead.") os.Exit(1) return "" } // TODO: support keys with special chars or fail func (x vim) EscapeKey(str string) string { return str } // TODO: Make sure this escaping is valid func (x vim) EscapeValue(str string) string { return "'" + strings.Replace(str, "'", "''", -1) + "'" } func (x vim) Export(key, value string) string { return "let $" + x.EscapeKey(key) + " = " + x.EscapeValue(value) + "\n" } func (x vim) Unset(key string) string { return "let $" + x.EscapeKey(key) + " = ''\n" } direnv-2.7.0/shell_zsh.go000066400000000000000000000011121257276462500153440ustar00rootroot00000000000000package main // ZSH is a singleton instance of ZSH_T type zsh int var ZSH zsh const ZSH_HOOK = ` _direnv_hook() { eval "$(direnv export zsh)"; } typeset -ag precmd_functions if [[ -z $precmd_functions[(r)_direnv_hook] ]]; then precmd_functions+=_direnv_hook; fi ` func (z zsh) Hook() string { return ZSH_HOOK } func (z zsh) Escape(str string) string { return ShellEscape(str) } func (z zsh) Export(key, value string) string { return "export " + z.Escape(key) + "=" + z.Escape(value) + ";" } func (z zsh) Unset(key string) string { return "unset " + z.Escape(key) + ";" } direnv-2.7.0/stdlib.go000066400000000000000000000275341257276462500146520ustar00rootroot00000000000000package main const STDLIB = "#!bash\n" + "#\n" + "# These are the commands available in an .envrc context\n" + "#\n" + "set -e\n" + "direnv=\"%s\"\n" + "\n" + "DIRENV_LOG_FORMAT=\"${DIRENV_LOG_FORMAT-direnv: %%s}\"\n" + "\n" + "# Usage: log_status [ ...]\n" + "#\n" + "# Logs a status message. Acts like echo,\n" + "# but wraps output in the standard direnv log format\n" + "# (controlled by $DIRENV_LOG_FORMAT), and directs it\n" + "# to stderr rather than stdout.\n" + "#\n" + "# Example:\n" + "#\n" + "# log_status \"Loading ...\"\n" + "#\n" + "log_status() {\n" + " if [[ -n $DIRENV_LOG_FORMAT ]]; then\n" + " local msg=$*\n" + " # shellcheck disable=SC2059\n" + " printf \"${DIRENV_LOG_FORMAT}\\n\" \"$msg\" >&2\n" + " fi\n" + "}\n" + "\n" + "# Usage: has \n" + "#\n" + "# Returns 0 if the is available. Returns 1 otherwise. It can be a\n" + "# binary in the PATH or a shell function.\n" + "#\n" + "# Example:\n" + "#\n" + "# if has curl; then\n" + "# echo \"Yes we do\"\n" + "# fi\n" + "#\n" + "has() {\n" + " type \"$1\" &>/dev/null\n" + "}\n" + "\n" + "# Usage: expand_path []\n" + "#\n" + "# Outputs the absolute path of relative to or the\n" + "# current directory.\n" + "#\n" + "# Example:\n" + "#\n" + "# cd /usr/local/games\n" + "# expand_path ../foo\n" + "# # output: /usr/local/foo\n" + "#\n" + "expand_path() {\n" + " \"$direnv\" expand_path \"$@\"\n" + "}\n" + "\n" + "# Usage: dotenv []\n" + "#\n" + "# Loads a \".env\" file into the current environment\n" + "#\n" + "dotenv() {\n" + " eval \"$(\"$direnv\" dotenv bash \"$@\")\"\n" + "}\n" + "\n" + "# Usage: user_rel_path \n" + "#\n" + "# Transforms an absolute path into a user-relative path if\n" + "# possible.\n" + "#\n" + "# Example:\n" + "#\n" + "# echo $HOME\n" + "# # output: /home/user\n" + "# user_rel_path /home/user/my/project\n" + "# # output: ~/my/project\n" + "# user_rel_path /usr/local/lib\n" + "# # output: /usr/local/lib\n" + "#\n" + "user_rel_path() {\n" + " local abs_path=${1#-}\n" + "\n" + " if [[ -z $abs_path ]]; then return; fi\n" + "\n" + " if [[ -n $HOME ]]; then\n" + " local rel_path=${abs_path#$HOME}\n" + " if [[ $rel_path != \"$abs_path\" ]]; then\n" + " abs_path=~$rel_path\n" + " fi\n" + " fi\n" + "\n" + " echo \"$abs_path\"\n" + "}\n" + "\n" + "# Usage: find_up \n" + "#\n" + "# Outputs the path of when searched from the current directory up to\n" + "# /. Returns 1 if the file has not been found.\n" + "#\n" + "# Example:\n" + "#\n" + "# cd /usr/local/my\n" + "# mkdir -p project/foo\n" + "# touch bar\n" + "# cd project/foo\n" + "# find_up bar\n" + "# # output: /usr/local/my/bar\n" + "#\n" + "find_up() {\n" + " (\n" + " cd \"$(pwd -P 2>/dev/null)\"\n" + " while true; do\n" + " if [[ -f $1 ]]; then\n" + " echo \"$PWD/$1\"\n" + " return 0\n" + " fi\n" + " if [[ $PWD = / ]] || [[ $PWD = // ]]; then\n" + " return 1\n" + " fi\n" + " cd ..\n" + " done\n" + " )\n" + "}\n" + "\n" + "# Usage: source_env \n" + "#\n" + "# Loads another \".envrc\" either by specifying its path or filename.\n" + "source_env() {\n" + " local rcpath=${1/#\\~/$HOME}\n" + " local rcfile\n" + " if ! [[ -f $rcpath ]]; then\n" + " rcpath=$rcpath/.envrc\n" + " fi\n" + " rcfile=$(user_rel_path \"$rcpath\")\n" + " pushd \"$(pwd -P 2>/dev/null)\" > /dev/null\n" + " pushd \"$(dirname \"$rcpath\")\" > /dev/null\n" + " if [[ -f ./$(basename \"$rcpath\") ]]; then\n" + " log_status \"loading $rcfile\"\n" + " . \"./$(basename \"$rcpath\")\"\n" + " else\n" + " log_status \"referenced $rcfile does not exist\"\n" + " fi\n" + " popd > /dev/null\n" + " popd > /dev/null\n" + "}\n" + "\n" + "# Usage: source_up []\n" + "#\n" + "# Loads another \".envrc\" if found with the find_up command.\n" + "#\n" + "source_up() {\n" + " local file=$1\n" + " local dir\n" + " if [[ -z $file ]]; then\n" + " file=.envrc\n" + " fi\n" + " dir=$(cd .. && find_up \"$file\")\n" + " if [[ -n $dir ]]; then\n" + " source_env \"$(user_rel_path \"$dir\")\"\n" + " fi\n" + "}\n" + "\n" + "# Usage: direnv_load \n" + "# e.g: direnv_load opam-env exec -- direnv dump\n" + "#\n" + "# Applies the environment generated by running as a\n" + "# command. This is useful for adopting the environment of a child\n" + "# process - cause that process to run \"direnv dump\" and then wrap\n" + "# the results with direnv_load.\n" + "#\n" + "direnv_load() {\n" + " local exports\n" + " exports=$(\"$direnv\" apply_dump <(\"$@\"))\n" + " local es=$?\n" + " if [[ $es -ne 0 ]]; then\n" + " return $es\n" + " fi\n" + " eval \"$exports\"\n" + "}\n" + "\n" + "# Usage: PATH_add \n" + "#\n" + "# Prepends the expanded to the PATH environment variable. It prevents a\n" + "# common mistake where PATH is replaced by only the new .\n" + "#\n" + "# Example:\n" + "#\n" + "# pwd\n" + "# # output: /home/user/my/project\n" + "# PATH_add bin\n" + "# echo $PATH\n" + "# # output: /home/user/my/project/bin:/usr/bin:/bin\n" + "#\n" + "PATH_add() {\n" + " PATH=$(expand_path \"$1\"):$PATH\n" + " export PATH\n" + "}\n" + "\n" + "# Usage: path_add \n" + "#\n" + "# Works like PATH_add except that it's for an arbitrary .\n" + "path_add() {\n" + " local old_paths=${!1}\n" + " local dir\n" + " dir=$(expand_path \"$2\")\n" + "\n" + " if [[ -z $old_paths ]]; then\n" + " old_paths=$dir\n" + " else\n" + " old_paths=$dir:$old_paths\n" + " fi\n" + "\n" + " export $1=$old_paths\n" + "}\n" + "\n" + "# Usage: load_prefix \n" + "#\n" + "# Expands some common path variables for the given prefix. This is\n" + "# useful if you installed something in the using\n" + "# $(./configure --prefix= && make install) and want to use it in \n" + "# the project.\n" + "#\n" + "# Variables set:\n" + "#\n" + "# CPATH\n" + "# LD_LIBRARY_PATH\n" + "# LIBRARY_PATH\n" + "# MANPATH\n" + "# PATH\n" + "# PKG_CONFIG_PATH\n" + "#\n" + "# Example:\n" + "#\n" + "# ./configure --prefix=$HOME/rubies/ruby-1.9.3\n" + "# make && make install\n" + "# # Then in the .envrc\n" + "# load_prefix ~/rubies/ruby-1.9.3\n" + "#\n" + "load_prefix() {\n" + " local dir\n" + " dir=$(expand_path \"$1\")\n" + " path_add CPATH \"$dir/include\"\n" + " path_add LD_LIBRARY_PATH \"$dir/lib\"\n" + " path_add LIBRARY_PATH \"$dir/lib\"\n" + " path_add MANPATH \"$dir/man\"\n" + " path_add MANPATH \"$dir/share/man\"\n" + " path_add PATH \"$dir/bin\"\n" + " path_add PKG_CONFIG_PATH \"$dir/lib/pkgconfig\"\n" + "}\n" + "\n" + "# Usage: layout \n" + "#\n" + "# A semantic dispatch used to describe common project layouts.\n" + "#\n" + "layout() {\n" + " local name=$1\n" + " shift\n" + " eval \"layout_$name\" \"$@\"\n" + "}\n" + "\n" + "# Usage: layout go\n" + "#\n" + "# Sets the GOPATH environment variable to the current directory.\n" + "#\n" + "layout_go() {\n" + " path_add GOPATH \"$PWD\"\n" + " PATH_add bin\n" + "}\n" + "\n" + "# Usage: layout node\n" + "#\n" + "# Adds \"$PWD/node_modules/.bin\" to the PATH environment variable.\n" + "layout_node() {\n" + " PATH_add node_modules/.bin\n" + "}\n" + "\n" + "# Usage: layout perl\n" + "#\n" + "# Setup environment variables required by perl's local::lib\n" + "# See http://search.cpan.org/dist/local-lib/lib/local/lib.pm for more details\n" + "#\n" + "layout_perl() {\n" + " local libdir=$PWD/.direnv/perl5\n" + " export LOCAL_LIB_DIR=$libdir\n" + " export PERL_MB_OPT=\"--install_base '$libdir'\"\n" + " export PERL_MM_OPT=\"INSTALL_BASE=$libdir\"\n" + " path_add PERL5LIB \"$libdir/lib/perl5\"\n" + " path_add PERL_LOCAL_LIB_ROOT \"$libdir\"\n" + " PATH_add \"$libdir/bin\"\n" + "}\n" + "\n" + "# Usage: layout python \n" + "#\n" + "# Creates and loads a virtualenv environment under\n" + "# \"$PWD/.direnv/python-$python_version\".\n" + "# This forces the installation of any egg into the project's sub-folder.\n" + "#\n" + "# It's possible to specify the python executable if you want to use different\n" + "# versions of python.\n" + "#\n" + "layout_python() {\n" + " local python=${1:-python}\n" + " local old_env=$PWD/.direnv/virtualenv\n" + " unset PYTHONHOME\n" + " if [[ -d $old_env && $python = python ]]; then\n" + " export VIRTUAL_ENV=$old_env\n" + " else\n" + " local python_version\n" + " python_version=$(\"$python\" -c \"import platform as p;print(p.python_version())\")\n" + " export VIRTUAL_ENV=$PWD/.direnv/python-$python_version\n" + " if [[ ! -d $VIRTUAL_ENV ]]; then\n" + " virtualenv \"--python=$python\" \"$VIRTUAL_ENV\"\n" + " fi\n" + " fi\n" + " PATH_add \"$VIRTUAL_ENV/bin\"\n" + "}\n" + "\n" + "# Usage: layout python3\n" + "#\n" + "# A shortcut for $(layout python python3)\n" + "#\n" + "layout_python3() {\n" + " layout_python python3\n" + "}\n" + "\n" + "# Usage: layout ruby\n" + "#\n" + "# Sets the GEM_HOME environment variable to \"$PWD/.direnv/ruby/RUBY_VERSION\".\n" + "# This forces the installation of any gems into the project's sub-folder.\n" + "# If you're using bundler it will create wrapper programs that can be invoked\n" + "# directly instead of using the $(bundle exec) prefix.\n" + "#\n" + "layout_ruby() {\n" + " if ruby -e \"exit Gem::VERSION > '2.2.0'\" 2>/dev/null; then\n" + " export GEM_HOME=$PWD/.direnv/ruby\n" + " else\n" + " local ruby_version\n" + " ruby_version=$(ruby -e\"puts (defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby') + '-' + RUBY_VERSION\")\n" + " export GEM_HOME=$PWD/.direnv/ruby-${ruby_version}\n" + " fi\n" + " export BUNDLE_BIN=$PWD/.direnv/bin\n" + "\n" + " PATH_add \"$GEM_HOME/bin\"\n" + " PATH_add \"$BUNDLE_BIN\"\n" + "}\n" + "\n" + "# Usage: use []\n" + "#\n" + "# A semantic command dispatch intended for loading external dependencies into\n" + "# the environment.\n" + "#\n" + "# Example:\n" + "#\n" + "# use_ruby() {\n" + "# echo \"Ruby $1\"\n" + "# }\n" + "# use ruby 1.9.3\n" + "# # output: Ruby 1.9.3\n" + "#\n" + "use() {\n" + " local cmd=$1\n" + " log_status \"using $*\"\n" + " shift\n" + " \"use_$cmd\" \"$@\"\n" + "}\n" + "\n" + "# Usage: use rbenv\n" + "#\n" + "# Loads rbenv which add the ruby wrappers available on the PATH.\n" + "#\n" + "use_rbenv() {\n" + " eval \"$(rbenv init -)\"\n" + "}\n" + "\n" + "# Usage: rvm [...]\n" + "#\n" + "# Should work just like in the shell if you have rvm installed.#\n" + "#\n" + "rvm() {\n" + " unset rvm\n" + " if [[ -n ${rvm_scripts_path:-} ]]; then\n" + " source \"${rvm_scripts_path}/rvm\"\n" + " elif [[ -n ${rvm_path:-} ]]; then\n" + " source \"${rvm_path}/scripts/rvm\"\n" + " else\n" + " source \"$HOME/.rvm/scripts/rvm\"\n" + " fi\n" + " rvm \"$@\"\n" + "}\n" + "\n" + "# Usage: use_nix [...]\n" + "#\n" + "# Load environment variables from `nix-shell`.\n" + "# If you have a `default.nix` or `shell.nix` these will be\n" + "# used by default, but you can also specify packages directly\n" + "# (e.g `use nix -p ocaml`).\n" + "#\n" + "use_nix() {\n" + " direnv_load nix-shell --show-trace \"$@\" --run 'direnv dump'\n" + "}\n" + "\n" + "## Load the global ~/.direnvrc if present\n" + "if [[ -f ${XDG_CONFIG_HOME:-$HOME/.config}/direnv/direnvrc ]]; then\n" + " source_env \"${XDG_CONFIG_HOME:-$HOME/.config}/direnv/direnvrc\" >&2\n" + "elif [[ -f $HOME/.direnvrc ]]; then\n" + " source_env \"$HOME/.direnvrc\" >&2\n" + "fi\n" direnv-2.7.0/stdlib.sh000066400000000000000000000215761257276462500146570ustar00rootroot00000000000000#!bash # # These are the commands available in an .envrc context # set -e direnv="%s" DIRENV_LOG_FORMAT="${DIRENV_LOG_FORMAT-direnv: %%s}" # Usage: log_status [ ...] # # Logs a status message. Acts like echo, # but wraps output in the standard direnv log format # (controlled by $DIRENV_LOG_FORMAT), and directs it # to stderr rather than stdout. # # Example: # # log_status "Loading ..." # log_status() { if [[ -n $DIRENV_LOG_FORMAT ]]; then local msg=$* # shellcheck disable=SC2059 printf "${DIRENV_LOG_FORMAT}\n" "$msg" >&2 fi } # Usage: has # # Returns 0 if the is available. Returns 1 otherwise. It can be a # binary in the PATH or a shell function. # # Example: # # if has curl; then # echo "Yes we do" # fi # has() { type "$1" &>/dev/null } # Usage: expand_path [] # # Outputs the absolute path of relative to or the # current directory. # # Example: # # cd /usr/local/games # expand_path ../foo # # output: /usr/local/foo # expand_path() { "$direnv" expand_path "$@" } # Usage: dotenv [] # # Loads a ".env" file into the current environment # dotenv() { eval "$("$direnv" dotenv bash "$@")" } # Usage: user_rel_path # # Transforms an absolute path into a user-relative path if # possible. # # Example: # # echo $HOME # # output: /home/user # user_rel_path /home/user/my/project # # output: ~/my/project # user_rel_path /usr/local/lib # # output: /usr/local/lib # user_rel_path() { local abs_path=${1#-} if [[ -z $abs_path ]]; then return; fi if [[ -n $HOME ]]; then local rel_path=${abs_path#$HOME} if [[ $rel_path != "$abs_path" ]]; then abs_path=~$rel_path fi fi echo "$abs_path" } # Usage: find_up # # Outputs the path of when searched from the current directory up to # /. Returns 1 if the file has not been found. # # Example: # # cd /usr/local/my # mkdir -p project/foo # touch bar # cd project/foo # find_up bar # # output: /usr/local/my/bar # find_up() { ( cd "$(pwd -P 2>/dev/null)" while true; do if [[ -f $1 ]]; then echo "$PWD/$1" return 0 fi if [[ $PWD = / ]] || [[ $PWD = // ]]; then return 1 fi cd .. done ) } # Usage: source_env # # Loads another ".envrc" either by specifying its path or filename. source_env() { local rcpath=${1/#\~/$HOME} local rcfile if ! [[ -f $rcpath ]]; then rcpath=$rcpath/.envrc fi rcfile=$(user_rel_path "$rcpath") pushd "$(pwd -P 2>/dev/null)" > /dev/null pushd "$(dirname "$rcpath")" > /dev/null if [[ -f ./$(basename "$rcpath") ]]; then log_status "loading $rcfile" . "./$(basename "$rcpath")" else log_status "referenced $rcfile does not exist" fi popd > /dev/null popd > /dev/null } # Usage: source_up [] # # Loads another ".envrc" if found with the find_up command. # source_up() { local file=$1 local dir if [[ -z $file ]]; then file=.envrc fi dir=$(cd .. && find_up "$file") if [[ -n $dir ]]; then source_env "$(user_rel_path "$dir")" fi } # Usage: direnv_load # e.g: direnv_load opam-env exec -- direnv dump # # Applies the environment generated by running as a # command. This is useful for adopting the environment of a child # process - cause that process to run "direnv dump" and then wrap # the results with direnv_load. # direnv_load() { local exports exports=$("$direnv" apply_dump <("$@")) local es=$? if [[ $es -ne 0 ]]; then return $es fi eval "$exports" } # Usage: PATH_add # # Prepends the expanded to the PATH environment variable. It prevents a # common mistake where PATH is replaced by only the new . # # Example: # # pwd # # output: /home/user/my/project # PATH_add bin # echo $PATH # # output: /home/user/my/project/bin:/usr/bin:/bin # PATH_add() { PATH=$(expand_path "$1"):$PATH export PATH } # Usage: path_add # # Works like PATH_add except that it's for an arbitrary . path_add() { local old_paths=${!1} local dir dir=$(expand_path "$2") if [[ -z $old_paths ]]; then old_paths=$dir else old_paths=$dir:$old_paths fi export $1=$old_paths } # Usage: load_prefix # # Expands some common path variables for the given prefix. This is # useful if you installed something in the using # $(./configure --prefix= && make install) and want to use it in # the project. # # Variables set: # # CPATH # LD_LIBRARY_PATH # LIBRARY_PATH # MANPATH # PATH # PKG_CONFIG_PATH # # Example: # # ./configure --prefix=$HOME/rubies/ruby-1.9.3 # make && make install # # Then in the .envrc # load_prefix ~/rubies/ruby-1.9.3 # load_prefix() { local dir dir=$(expand_path "$1") path_add CPATH "$dir/include" path_add LD_LIBRARY_PATH "$dir/lib" path_add LIBRARY_PATH "$dir/lib" path_add MANPATH "$dir/man" path_add MANPATH "$dir/share/man" path_add PATH "$dir/bin" path_add PKG_CONFIG_PATH "$dir/lib/pkgconfig" } # Usage: layout # # A semantic dispatch used to describe common project layouts. # layout() { local name=$1 shift eval "layout_$name" "$@" } # Usage: layout go # # Sets the GOPATH environment variable to the current directory. # layout_go() { path_add GOPATH "$PWD" PATH_add bin } # Usage: layout node # # Adds "$PWD/node_modules/.bin" to the PATH environment variable. layout_node() { PATH_add node_modules/.bin } # Usage: layout perl # # Setup environment variables required by perl's local::lib # See http://search.cpan.org/dist/local-lib/lib/local/lib.pm for more details # layout_perl() { local libdir=$PWD/.direnv/perl5 export LOCAL_LIB_DIR=$libdir export PERL_MB_OPT="--install_base '$libdir'" export PERL_MM_OPT="INSTALL_BASE=$libdir" path_add PERL5LIB "$libdir/lib/perl5" path_add PERL_LOCAL_LIB_ROOT "$libdir" PATH_add "$libdir/bin" } # Usage: layout python # # Creates and loads a virtualenv environment under # "$PWD/.direnv/python-$python_version". # This forces the installation of any egg into the project's sub-folder. # # It's possible to specify the python executable if you want to use different # versions of python. # layout_python() { local python=${1:-python} local old_env=$PWD/.direnv/virtualenv unset PYTHONHOME if [[ -d $old_env && $python = python ]]; then export VIRTUAL_ENV=$old_env else local python_version python_version=$("$python" -c "import platform as p;print(p.python_version())") export VIRTUAL_ENV=$PWD/.direnv/python-$python_version if [[ ! -d $VIRTUAL_ENV ]]; then virtualenv "--python=$python" "$VIRTUAL_ENV" fi fi PATH_add "$VIRTUAL_ENV/bin" } # Usage: layout python3 # # A shortcut for $(layout python python3) # layout_python3() { layout_python python3 } # Usage: layout ruby # # Sets the GEM_HOME environment variable to "$PWD/.direnv/ruby/RUBY_VERSION". # This forces the installation of any gems into the project's sub-folder. # If you're using bundler it will create wrapper programs that can be invoked # directly instead of using the $(bundle exec) prefix. # layout_ruby() { if ruby -e "exit Gem::VERSION > '2.2.0'" 2>/dev/null; then export GEM_HOME=$PWD/.direnv/ruby else local ruby_version ruby_version=$(ruby -e"puts (defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby') + '-' + RUBY_VERSION") export GEM_HOME=$PWD/.direnv/ruby-${ruby_version} fi export BUNDLE_BIN=$PWD/.direnv/bin PATH_add "$GEM_HOME/bin" PATH_add "$BUNDLE_BIN" } # Usage: use [] # # A semantic command dispatch intended for loading external dependencies into # the environment. # # Example: # # use_ruby() { # echo "Ruby $1" # } # use ruby 1.9.3 # # output: Ruby 1.9.3 # use() { local cmd=$1 log_status "using $*" shift "use_$cmd" "$@" } # Usage: use rbenv # # Loads rbenv which add the ruby wrappers available on the PATH. # use_rbenv() { eval "$(rbenv init -)" } # Usage: rvm [...] # # Should work just like in the shell if you have rvm installed.# # rvm() { unset rvm if [[ -n ${rvm_scripts_path:-} ]]; then source "${rvm_scripts_path}/rvm" elif [[ -n ${rvm_path:-} ]]; then source "${rvm_path}/scripts/rvm" else source "$HOME/.rvm/scripts/rvm" fi rvm "$@" } # Usage: use_nix [...] # # Load environment variables from `nix-shell`. # If you have a `default.nix` or `shell.nix` these will be # used by default, but you can also specify packages directly # (e.g `use nix -p ocaml`). # use_nix() { direnv_load nix-shell --show-trace "$@" --run 'direnv dump' } ## Load the global ~/.direnvrc if present if [[ -f ${XDG_CONFIG_HOME:-$HOME/.config}/direnv/direnvrc ]]; then source_env "${XDG_CONFIG_HOME:-$HOME/.config}/direnv/direnvrc" >&2 elif [[ -f $HOME/.direnvrc ]]; then source_env "$HOME/.direnvrc" >&2 fi direnv-2.7.0/test/000077500000000000000000000000001257276462500140065ustar00rootroot00000000000000direnv-2.7.0/test/.envrc000066400000000000000000000000001257276462500151120ustar00rootroot00000000000000direnv-2.7.0/test/direnv-test.fish000077500000000000000000000017161257276462500171350ustar00rootroot00000000000000#!/usr/bin/env fish function eq --argument-names a b if not test (count $argv) = 2 echo "Error: " (count $argv) " arguments passed to `eq`: $argv" exit 1 end if not test $a = $b printf "Error:\n - expected: %s\n - got: %s\n" "$a" "$b" exit 1 end end cd (dirname (status -f)) set TEST_DIR $PWD set -gx PATH (dirname $TEST_DIR) $PATH # Reset the direnv loading if any set -x DIRENV_CONFIG $PWD set -e DIRENV_BASH set -e DIRENV_DIR set -e DIRENV_MTIME set -e DIRENV_DIFF function direnv_eval #direnv export fish # for debugging eval (direnv export fish) end function test_start -a name cd "$TEST_DIR/scenarios/$name" direnv allow echo "## Testing $name ##" pwd end function test_stop cd $TEST_DIR direnv_eval end ### RUN ### direnv allow direnv_eval test_start dump set -ex LS_COLORS direnv_eval eq "$LS_COLORS" "*.ogg=38;5;45:*.wav=38;5;45" eq "$LESSOPEN" "||/usr/bin/lesspipe.sh %s" eq "$THREE_BACKSLASHES" "\\\\\\" test_stop direnv-2.7.0/test/direnv-test.sh000077500000000000000000000046031257276462500166140ustar00rootroot00000000000000#!/usr/bin/env bash set -e cd `dirname $0` TEST_DIR=$PWD export PATH=`dirname $TEST_DIR`:$PATH # Reset the direnv loading if any export DIRENV_CONFIG=$PWD unset DIRENV_BASH unset DIRENV_DIR unset DIRENV_MTIME unset DIRENV_DIFF direnv_eval() { eval `direnv export bash` } test_start() { cd "$TEST_DIR/scenarios/$1" direnv allow echo "## Testing $1 ##" } test_stop() { cd $TEST_DIR direnv_eval } ### RUN ### direnv allow || true direnv_eval test_start base direnv_eval test "$HELLO" = "world" MTIME=$DIRENV_MTIME direnv_eval test "$MTIME" = "$DIRENV_MTIME" sleep 1 touch .envrc direnv_eval test "$MTIME" != "$DIRENV_MTIME" cd .. direnv_eval echo "${HELLO}" test -z "${HELLO}" test_stop test_start inherit direnv_eval test "$HELLO" = "world" test_stop test_start "ruby-layout" direnv_eval test "$GEM_HOME" != "" test_stop # Make sure directories with spaces are fine test_start "space dir" direnv_eval test "$SPACE_DIR" = "true" test_stop test_start "child-env" direnv_eval test "$PARENT_PRE" = "1" test "$CHILD" = "1" test "$PARENT_POST" = "1" test -z "$REMOVE_ME" test_stop test_start "special-vars" export DIRENV_BASH=`which bash` export DIRENV_CONFIG=foobar direnv_eval || true test -n "$DIRENV_BASH" test "$DIRENV_CONFIG" = "foobar" unset DIRENV_BASH unset DIRENV_CONFIG test_stop test_start "dump" direnv_eval test "$LS_COLORS" = "*.ogg=38;5;45:*.wav=38;5;45" test "$THREE_BACKSLASHES" = '\\\' test "$LESSOPEN" = "||/usr/bin/lesspipe.sh %s" test_stop test_start "empty-var" direnv_eval test "${FOO-unset}" != "unset" test "${FOO}" = "" test_stop test_start "empty-var-unset" export FOO="" direnv_eval test "${FOO-unset}" == "unset" unset FOO test_stop test_start "missing-file-source-env" direnv_eval test_stop # Context: foo/bar is a symlink to ../baz. foo/ contains and .envrc file # BUG: foo/bar is resolved in the .envrc execution context and so can't find # the .envrc file. # # Apparently, the CHDIR syscall does that so I don't know how to work around # the issue. # # test_start "symlink-bug" # cd foo/bar # direnv_eval # test_stop # Pending: test that the mtime is looked on the original file # test_start "utils" # LINK_TIME=`direnv file-mtime link-to-somefile` # touch somefile # NEW_LINK_TIME=`direnv file-mtime link-to-somefile` # test "$LINK_TIME" = "$NEW_LINK_TIME" # test_stop direnv-2.7.0/test/direnv-test.tcsh000077500000000000000000000055511257276462500171460ustar00rootroot00000000000000#!/usr/bin/env tcsh -e -x # set -e cd `dirname $0` setenv TEST_DIR $PWD setenv PATH `dirname $TEST_DIR`:$PATH # Reset the direnv loading if any setenv DIRENV_CONFIG $PWD unsetenv DIRENV_BASH unsetenv DIRENV_DIR unsetenv DIRENV_MTIME unsetenv DIRENV_DIFF # direnv_eval() { # eval `direnv export bash` # } alias direnv_eval 'eval `direnv export tcsh`' # test_start() { # cd "$TEST_DIR/scenarios/$1" # direnv allow # echo "## Testing $1 ##" # } # test_stop { # cd $TEST_DIR # direnv_eval # } ### RUN ### direnv allow || true direnv_eval cd $TEST_DIR/scenarios/base echo "Testing base" direnv_eval test "$HELLO" = "world" setenv MTIME $DIRENV_MTIME direnv_eval test "$MTIME" = "$DIRENV_MTIME" sleep 1 touch .envrc direnv_eval test "$MTIME" != "$DIRENV_MTIME" cd .. direnv_eval echo "$?HELLO" test 0 -eq "$?HELLO" cd $TEST_DIR ; direnv_eval cd $TEST_DIR/scenarios/inherit direnv allow echo "Testing inherit" direnv_eval test "$HELLO" = "world" cd $TEST_DIR ; direnv_eval cd $TEST_DIR/scenarios/ruby-layout direnv allow echo "Testing ruby-layout" direnv_eval test "$GEM_HOME" != "" cd $TEST_DIR ; direnv_eval # Make sure directories with spaces are fine cd $TEST_DIR/scenarios/"space dir" direnv allow echo "Testing space dir" direnv_eval test "$SPACE_DIR" = "true" cd $TEST_DIR ; direnv_eval cd $TEST_DIR/scenarios/child-env direnv allow echo "Testing child-env" direnv_eval test "$PARENT_PRE" = "1" test "$CHILD" = "1" test "$PARENT_POST" = "1" test 0 -eq "$?REMOVE_ME" cd $TEST_DIR ; direnv_eval # cd $TEST_DIR/scenarios/special-vars # direnv allow # echo "Testing special-vars" # setenv DIRENV_BASH `which bash` # setenv DIRENV_CONFIG foobar # direnv_eval || true # test -n "$DIRENV_BASH" # test "$DIRENV_CONFIG" = "foobar" # unsetenv DIRENV_BASH # unsetenv DIRENV_CONFIG # cd $TEST_DIR ; direnv_eval cd $TEST_DIR/scenarios/"empty-var" direnv allow echo "Testing empty-var" direnv_eval test "$?FOO" -eq 1 test "$FOO" = "" cd $TEST_DIR ; direnv_eval cd $TEST_DIR/scenarios/"empty-var-unset" direnv allow echo "Testing empty-var-unset" setenv FOO "" direnv_eval test "$?FOO" -eq '0' unsetenv FOO cd $TEST_DIR ; direnv_eval # Context: foo/bar is a symlink to ../baz. foo/ contains and .envrc file # BUG: foo/bar is resolved in the .envrc execution context and so can't find # the .envrc file. # # Apparently, the CHDIR syscall does that so I don't know how to work around # the issue. # # cd $TEST_DIR/scenarios/"symlink-bug" # cd foo/bar # direnv_eval # cd $TEST_DIR ; direnv_eval # Pending: test that the mtime is looked on the original file # cd $TEST_DIR/scenarios/"utils" # LINK_TIME=`direnv file-mtime link-to-somefile` # touch somefile # NEW_LINK_TIME=`direnv file-mtime link-to-somefile` # test "$LINK_TIME" = "$NEW_LINK_TIME" # cd $TEST_DIR ; direnv_eval direnv-2.7.0/test/scenarios/000077500000000000000000000000001257276462500157745ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/base/000077500000000000000000000000001257276462500167065ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/base/.envrc000066400000000000000000000000231257276462500200170ustar00rootroot00000000000000export HELLO=world direnv-2.7.0/test/scenarios/child-env/000077500000000000000000000000001257276462500176455ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/child-env/.envrc000066400000000000000000000002061257276462500207610ustar00rootroot00000000000000set -e export PARENT_PRE=1 export REMOVE_ME=1 direnv_load bash -c 'export CHILD=1; unset REMOVE_ME; direnv dump' export PARENT_POST=1 direnv-2.7.0/test/scenarios/dump/000077500000000000000000000000001257276462500167415ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/dump/.envrc000066400000000000000000000002301257276462500200520ustar00rootroot00000000000000direnv_load env \ LS_COLORS='*.ogg=38;5;45:*.wav=38;5;45' \ LESSOPEN='||/usr/bin/lesspipe.sh %s' \ THREE_BACKSLASHES='\\\' \ bash -c "direnv dump" direnv-2.7.0/test/scenarios/empty-var-unset/000077500000000000000000000000001257276462500210545ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/empty-var-unset/.envrc000066400000000000000000000000111257276462500221620ustar00rootroot00000000000000unset FOOdirenv-2.7.0/test/scenarios/empty-var/000077500000000000000000000000001257276462500177205ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/empty-var/.envrc000066400000000000000000000000151257276462500210320ustar00rootroot00000000000000export FOO=""direnv-2.7.0/test/scenarios/inherit/000077500000000000000000000000001257276462500174365ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/inherit/.envrc000066400000000000000000000000421257276462500205500ustar00rootroot00000000000000source ../base/.envrc echo $HELLO direnv-2.7.0/test/scenarios/missing-file-source-env/000077500000000000000000000000001257276462500224465ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/missing-file-source-env/.envrc000066400000000000000000000000451257276462500235630ustar00rootroot00000000000000source_env "$(mktemp -d -t $RANDOM)" direnv-2.7.0/test/scenarios/ruby-layout.env000066400000000000000000000001731257276462500210030ustar00rootroot00000000000000export PATH=`pwd`/ruby-layout/bin:$PATH export RUBYLIB=`pwd`/ruby-layout/lib export GEM_HOME=`pwd`/ruby-layout/vendor/gems direnv-2.7.0/test/scenarios/ruby-layout/000077500000000000000000000000001257276462500202705ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/ruby-layout/.envrc000066400000000000000000000000161257276462500214030ustar00rootroot00000000000000layout "ruby" direnv-2.7.0/test/scenarios/space dir/000077500000000000000000000000001257276462500176265ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/space dir/.envrc000066400000000000000000000000451257276462500207430ustar00rootroot00000000000000PATH_add "bin" export SPACE_DIR=true direnv-2.7.0/test/scenarios/special-vars/000077500000000000000000000000001257276462500203655ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/special-vars/.envrc000066400000000000000000000000001257276462500214710ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/symlink-bug/000077500000000000000000000000001257276462500202355ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/symlink-bug/baz/000077500000000000000000000000001257276462500210115ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/symlink-bug/baz/.keep000066400000000000000000000000001257276462500217240ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/symlink-bug/foo/000077500000000000000000000000001257276462500210205ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/symlink-bug/foo/.envrc000066400000000000000000000000001257276462500221240ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/symlink-bug/foo/bar000077700000000000000000000000001257276462500216402..ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/use-rvm/000077500000000000000000000000001257276462500173725ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/use-rvm/.envrc000066400000000000000000000000161257276462500205050ustar00rootroot00000000000000rvm use 1.8.7 direnv-2.7.0/test/scenarios/utils/000077500000000000000000000000001257276462500171345ustar00rootroot00000000000000direnv-2.7.0/test/scenarios/utils/link-to-file000077700000000000000000000000001257276462500230722somefileustar00rootroot00000000000000direnv-2.7.0/test/scenarios/utils/somefile000066400000000000000000000000001257276462500206500ustar00rootroot00000000000000direnv-2.7.0/test/show-direnv-diff.sh000077500000000000000000000003341257276462500175200ustar00rootroot00000000000000#!/bin/sh # # Small util to display the content of the current DIRENV_DIFF env var. # GZIP_HEADER="\x1f\x8b\x08\x00\x00\x00\x00\x00" (printf $GZIP_HEADER; echo $DIRENV_DIFF | base64 -D) | gzip -dc | python -mjson.tool direnv-2.7.0/version.go000066400000000000000000000000461257276462500150430ustar00rootroot00000000000000package main const VERSION = "2.7.0" direnv-2.7.0/xdg.go000066400000000000000000000010031257276462500141320ustar00rootroot00000000000000package main import ( "path/filepath" ) // Loosely // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html // We don't handle XDG_CONFIG_DIRS yet func XdgConfigDir(env Env, programName string) string { if env["XDG_CONFIG_HOME"] != "" { return filepath.Join(env["XDG_CONFIG_HOME"], programName) } else if env["HOME"] != "" { return filepath.Join(env["HOME"], ".config", programName) } // In theory we could also read /etc/passwd and look for the home matching the process' Uid return "" }