├── .github └── FUNDING.yml ├── .gitignore ├── CONTRIBUTING.markdown ├── README.markdown ├── addon-info.json ├── doc └── dotenv.txt └── plugin └── dotenv.vim /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: tpope 2 | custom: ["https://www.paypal.me/vimpope"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/tags 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.markdown: -------------------------------------------------------------------------------- 1 | See the [contribution guidelines for pathogen.vim](https://github.com/tpope/vim-pathogen/blob/master/CONTRIBUTING.markdown). 2 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # dotenv.vim 2 | 3 | This plugin provides basic support for `.env` and `Procfile`. 4 | 5 | ## Interactive Usage 6 | 7 | Use `:Dotenv {file}` or `:Dotenv {dir}` to load a `.env` file and set the 8 | corresponding environment variables in Vim. Use `:verbose Dotenv` to see what 9 | variables are actually being set. 10 | 11 | ## Projections 12 | 13 | With [projectionist.vim][] and [dispatch.vim][] installed, you'll get a 14 | default `:Start` of `foreman start` for projects with a `Procfile`, and a 15 | default `:Dispatch` of `foreman check` for the `Procfile` itself. 16 | 17 | [projectionist.vim]: https://github.com/tpope/vim-projectionist 18 | [dispatch.vim]: https://github.com/tpope/vim-dispatch 19 | 20 | ## Dispatch 21 | 22 | If you call `:Dispatch foreman run whatever` or `:Dispatch dotenv whatever`, 23 | the compiler will be correctly selected for the `whatever` command. 24 | 25 | ## API 26 | 27 | While the above are all marginally helpful, this is the use case that inspired 28 | the plugin. Other plugins can call `DotenvGet('VAR')` to get the value of 29 | `$VAR` globally or from the current buffer's `.env`. Here's a wrapper to 30 | optionally use `DotenvGet()` if it's available. 31 | 32 | function! s:env(var) abort 33 | return exists('*DotenvGet') ? DotenvGet(a:var) : eval('$'.a:var) 34 | endfunction 35 | 36 | let db_url = s:env('DATABASE_URL') 37 | 38 | There's also `DotenvExpand()`, a drop-in replacement for `expand()`. 39 | 40 | function! s:expand(expr) abort 41 | return exists('*DotenvExpand') ? DotenvExpand(a:expr) : expand(a:expr) 42 | endfunction 43 | 44 | ## License 45 | 46 | Copyright © Tim Pope. Distributed under the same terms as Vim itself. 47 | See `:help license`. 48 | -------------------------------------------------------------------------------- /addon-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "vim_script_nr": 5176, 3 | "name": "dotenv", 4 | "version": "1.0", 5 | "author": "Tim Pope ", 6 | "repository" : {"type": "git", "url": "git://github.com/tpope/vim-dotenv.git"}, 7 | "description": "Basic support for .env and Procfile" 8 | } 9 | -------------------------------------------------------------------------------- /doc/dotenv.txt: -------------------------------------------------------------------------------- 1 | *dotenv.txt* Basic support for .env and Procfile 2 | 3 | Author: Tim Pope 4 | Repo: https://github.com/tpope/vim-dotenv 5 | License: Same terms as Vim itself (see |license|) 6 | 7 | USAGE *dotenv* *:Dotenv* 8 | 9 | :Dotenv {file} Load the dotenv file {file} and assign the given 10 | environment variables globally in the current Vim 11 | session. Use |:verbose| to see what actually gets 12 | assigned. 13 | 14 | :Dotenv {dir} Equivalent to :Dotenv {dir}/.env. 15 | 16 | :Dotenv Show the environment for the current buffer. 17 | 18 | API *dotenv-api* 19 | 20 | See the top of plugin/dotenv.vim. 21 | 22 | vim:tw=78:et:ft=help:norl: 23 | -------------------------------------------------------------------------------- /plugin/dotenv.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_dotenv') 2 | finish 3 | endif 4 | let g:loaded_dotenv = 1 5 | 6 | " Get the value of a variable from the global Vim environment or current 7 | " buffer's .env. 8 | function! DotenvGet(...) abort 9 | if !exists('b:dotenv') 10 | let b:dotenv = DotenvRead() 11 | endif 12 | let env = b:dotenv 13 | if !a:0 14 | " Use this to get the current .env, as b:dotenv is private. 15 | return env 16 | endif 17 | let key = substitute(a:1, '^\$', '', '') 18 | return exists('$'.key) ? eval('$'.key) : get(env, key, (a:0 > 1 ? a:2 : '')) 19 | endfunction 20 | 21 | " Drop in replacement for expand() that takes the current buffer's .env into 22 | " account. 23 | function! DotenvExpand(str, ...) abort 24 | let str = a:str 25 | let pat = '\$\(\w\+\)' 26 | let end = 0 27 | while 1 28 | let pos = match(str, pat, end) 29 | if pos < 0 30 | break 31 | endif 32 | let var = matchstr(str, pat, end) 33 | let end = pos + len(var) 34 | let val = DotenvGet(var) 35 | let str = strpart(str, 0, pos) . (empty(val) ? var : fnameescape(val)) . strpart(str, end) 36 | endwhile 37 | return call('expand', [str] + a:000) 38 | endfunction 39 | 40 | " Find the nearest .env file. 41 | function! DotenvFile() abort 42 | return findfile('.env', isdirectory(expand('%')) ? expand('%').';' : '.;') 43 | endfunction 44 | 45 | " Read and parse a .env file. 46 | function! DotenvRead(...) abort 47 | let env = {} 48 | for file in a:0 ? a:000 : [DotenvFile()] 49 | if len(file) 50 | call s:read_env(isdirectory(file) ? file.'/.env' : file, env) 51 | endif 52 | endfor 53 | lockvar! env 54 | return env 55 | endfunction 56 | 57 | " Section: Implementation 58 | 59 | function! s:lookup(key, env) abort 60 | if a:key ==# '\n' 61 | return "\n" 62 | elseif a:key =~# '^\\' 63 | return a:key[1:-1] 64 | endif 65 | let var = matchstr(a:key, '^\${\zs.*\ze}$\|^\$\zs\(.*\)$') 66 | if exists('$'.var) 67 | return eval('$'.var) 68 | else 69 | return get(a:env, var, '') 70 | endif 71 | endfunction 72 | 73 | let s:env_cache = {} 74 | let s:interpolation = '\\\=\${.\{-\}}\|\\\=\$\w\+' 75 | 76 | function! s:read_env(file, ...) abort 77 | let file = fnamemodify(a:file, ':p') 78 | let ftime = getftime(file) 79 | if ftime < 0 80 | return {} 81 | endif 82 | let [cachetime, lines] = get(s:env_cache, file, [-2, []]) 83 | if ftime != cachetime 84 | let lines = [] 85 | for line in readfile(file) 86 | let matches = matchlist(line, '\v\C^%(export\s+)=([[:alnum:]_.]+)%(\s*\=\s*|:\s{-})(''%(\\''|[^''])*''|"%(\\"|[^"])*"|[^#]+)=%( *#.*)?$') 87 | if !empty(matches) 88 | call add(lines, matches[1:2]) 89 | endif 90 | endfor 91 | let s:env_cache[file] = [ftime, lines] 92 | endif 93 | let env = a:0 ? a:1 : {} 94 | for [key, value] in lines 95 | if !has_key(env, key) 96 | if value =~# '^\s*".*"\s*$' 97 | let value = substitute(value, '\n', "\n", 'g') 98 | let value = substitute(value, '\\\ze[^$]', '', 'g') 99 | endif 100 | let value = substitute(value, '^\s*\([''"]\)\=\(.\{-\}\)\1\s*$', '\2', '') 101 | let value = substitute(value, s:interpolation, '\=s:lookup(submatch(0), env)', 'g') 102 | let env[key] = value 103 | endif 104 | endfor 105 | return env 106 | endfunction 107 | 108 | function! s:echo_let(var, val) abort 109 | echohl Statement 110 | echon 'let ' 111 | echohl PreProc 112 | echon '$'.a:var 113 | echohl Operator 114 | echon ' = ' 115 | echohl String 116 | echon string(a:val) 117 | echohl None 118 | endfunction 119 | 120 | function! s:Load(bang, ...) abort 121 | if !a:0 122 | let file = DotenvFile() 123 | if empty(file) 124 | echohl Comment 125 | echo "# No dotenv found" 126 | echohl None 127 | elseif a:bang 128 | return 'edit '.fnameescape(file) 129 | else 130 | echohl Comment 131 | echon (&verbose ? '" ' : '# ').fnamemodify(file, ':~:.') 132 | echohl None 133 | let env = DotenvGet() 134 | for var in sort(keys(env)) 135 | echon "\n" 136 | if &verbose 137 | call s:echo_let(var, env[var]) 138 | else 139 | echohl PreProc 140 | echon var 141 | echohl Operator 142 | echon '=' 143 | echohl String 144 | echon escape(env[var], ' $"''') 145 | echohl None 146 | endif 147 | endfor 148 | endif 149 | return '' 150 | endif 151 | let files = [] 152 | for glob in a:000 153 | if glob =~# '[*]' 154 | call extend(files, split(glob(glob), "\n")) 155 | else 156 | call extend(files, split(expand(glob), "\n")) 157 | endif 158 | endfor 159 | if !a:bang 160 | for file in files 161 | if !filereadable(file) && !filereadable(file.'/.env') 162 | return 'echoerr '.string('No .env found at '.file) 163 | endif 164 | endfor 165 | endif 166 | let env = call('DotenvRead', files) 167 | let first = 1 168 | for var in sort(keys(env)) 169 | if &verbose 170 | if first 171 | let first = 0 172 | else 173 | echon "\n" 174 | endif 175 | call s:echo_let(var, env[var]) 176 | endif 177 | execute 'let $'.var '= env[var]' 178 | endfor 179 | return '' 180 | endfunction 181 | 182 | command! -bar -bang -nargs=* -complete=file Dotenv exe s:Load(0, ) 183 | 184 | if !exists('g:dispatch_compilers') 185 | let g:dispatch_compilers = {} 186 | endif 187 | let g:dispatch_compilers['dotenv'] = '' 188 | let g:dispatch_compilers['foreman run'] = '' 189 | 190 | if !exists('g:projectionist_heuristics') 191 | let g:projectionist_heuristics = {} 192 | endif 193 | if !has_key(g:projectionist_heuristics, "Procfile") 194 | let g:projectionist_heuristics["Procfile"] = { 195 | \ "Procfile": {"dispatch": get(g:, "foreman", "foreman") . " check"}} 196 | endif 197 | 198 | augroup dotenvPlugin 199 | autocmd BufNewFile,BufReadPost .env.* setfiletype sh 200 | 201 | autocmd BufNewFile,BufReadPre * unlet! b:dotenv 202 | autocmd FileType netrw unlet! b:dotenv 203 | augroup END 204 | --------------------------------------------------------------------------------