From 1a5c3b1a46032047ddd0ce62d392aa2b9fb11d9c Mon Sep 17 00:00:00 2001 From: Eric Siegerman Date: Mon, 12 May 2014 20:13:03 -0400 Subject: [PATCH 1/6] Expand "Yes/No" answers to "Yes/No/Always/neVer" --- README.md | 21 +++++++++++++++++++-- plugin/localvimrc.vim | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b820950..fc4a16b 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,25 @@ Features: is traversed and special files such as .(local-)vimrc files are sourced - Because you don't want to run untrusted code by accident, this plugin - calculates a hash before sourcing. If its unknown you must confirm sourcing - the file. The hash is updated automatically if you write a local vimrc file. + calculates a asks you before sourcing any given file, and optionally + remembers your answer for the future. Possible answers are (with the + abbreviation captitalized): + + - `Yes`: Read the file this time, but do not remember the answer; + you will be asked again next time + + - `No`: Don't read it, and don't remember the answer + + - `Always`: Read the file this time, and automatically do so from now on + + - `neVer`: Don't read it, and don't ask again in future + +- It also remembers a hash of the file's contents, so that if the file + changes, you'll be asked again. + + If you use vim to edit a local vimrc file, an answer of "Always" is + automatically saved for (that version of) the file, on the theory + that you trust any edits you yourself have made. - if you change a directory and edit a file the local vimrc files are resourced diff --git a/plugin/localvimrc.vim b/plugin/localvimrc.vim index d2bb51f..d7d3d18 100644 --- a/plugin/localvimrc.vim +++ b/plugin/localvimrc.vim @@ -10,6 +10,7 @@ let s:c.hash_fun = get(s:c,'hash_fun','LVRHashOfFile') let s:c.cache_file = get(s:c,'cache_file', $HOME.'/.vim_local_rc_cache') let s:c.resource_on_cwd_change = get(s:c, 'resource_on_cwd_change', 1) let s:last_cwd = '' +let s:answer_map = ['ANS_GOK', 'ANS_YES', 'ANS_NO', 'ANS_ALWAYS', 'ANS_NEVER'] " very simple hash function using md5 falling back to VimL implementation fun! LVRHashOfFile(file, seed) @@ -26,6 +27,17 @@ fun! LVRHashOfFile(file, seed) endif endfun +" Ask the user; return one of the ANS_* strings. +" If they don't provide a useful answer, return dflt. +fun! LVRAsk(prompt, dflt) + let ans = confirm(a:prompt,"&Yes\n&No\n&Always\nNe&Ver", a:dflt) + if 0 < ans + return s:answer_map[ans] + else + return dflt + endif +endfun + " source local vimrc, ask user for confirmation if file contents change fun! LVRSource(file, cache) " always ignore user global .vimrc which Vim sources on startup: @@ -34,10 +46,27 @@ fun! LVRSource(file, cache) let p = expand(a:file) let h = call(function(s:c.hash_fun), [a:file, a:cache.seed]) " if hash doesn't match or no hash exists ask user to confirm sourcing this file - if get(a:cache, p, 'no-hash') == h || 1 == confirm('source '.p,"&Y\n&n",2) - let a:cache[p] = h + let ce = get(a:cache, p, {'hash':'no-hash', 'ans':'ANS_NO'}) + if ce['hash'] == h + let ans = ce['ans'] + else + let ans = LVRAsk('source '.p,'ANS_NO') + endif + " source the file if so requested + if 'ANS_YES' == ans || 'ANS_ALWAYS' == ans exec 'source '.fnameescape(p) endif + " update the cache + if 'ANS_ALWAYS' == ans || 'ANS_NEVER' == ans + let ce = {'hash': h, 'ans': ans} + let a:cache[p] = ce + else + " user doesn't want answer saved; delete the cache entry entirely + " N.B.: "!" doesn't suppress missing-key error from "unlet" :-(, + " so we make sure the entry is defined before nuking it + let a:cache[p] = {} + unlet a:cache[p] + endif endf fun! LVRWithCache(F, args) From eaf592d31cfb349bebe9475001b0e3cfe3e5fc2c Mon Sep 17 00:00:00 2001 From: Eric Siegerman Date: Mon, 12 May 2014 20:31:36 -0400 Subject: [PATCH 2/6] Adapt LVRUpdateCache() to the new cache format. --- plugin/localvimrc.vim | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/plugin/localvimrc.vim b/plugin/localvimrc.vim index d7d3d18..94c8368 100644 --- a/plugin/localvimrc.vim +++ b/plugin/localvimrc.vim @@ -38,6 +38,19 @@ fun! LVRAsk(prompt, dflt) endif endfun +fun! LVRSaveAnswer(cache, key, hash, ans) + if 'ANS_ALWAYS' == a:ans || 'ANS_NEVER' == a:ans + let ce = {'hash': a:hash, 'ans': a:ans} + let a:cache[a:key] = ce + else + " user doesn't want answer saved; delete the cache entry entirely + " N.B.: "unlet!" doesn't suppress missing-key error :-(, so instead + " we make sure the entry is defined before undefining it + let a:cache[a:key] = {} + unlet a:cache[a:key] + endif +endf + " source local vimrc, ask user for confirmation if file contents change fun! LVRSource(file, cache) " always ignore user global .vimrc which Vim sources on startup: @@ -56,17 +69,7 @@ fun! LVRSource(file, cache) if 'ANS_YES' == ans || 'ANS_ALWAYS' == ans exec 'source '.fnameescape(p) endif - " update the cache - if 'ANS_ALWAYS' == ans || 'ANS_NEVER' == ans - let ce = {'hash': h, 'ans': ans} - let a:cache[p] = ce - else - " user doesn't want answer saved; delete the cache entry entirely - " N.B.: "!" doesn't suppress missing-key error from "unlet" :-(, - " so we make sure the entry is defined before nuking it - let a:cache[p] = {} - unlet a:cache[p] - endif + call LVRSaveAnswer(a:cache, p, h, ans) endf fun! LVRWithCache(F, args) @@ -114,7 +117,8 @@ SourceLocalVimrcOnce " if its you writing a file update hash automatically fun! LVRUpdateCache(cache) let f = expand('%:p') - let a:cache[f] = call(function(s:c.hash_fun), [f, a:cache.seed]) + call LVRSaveAnswer(a:cache, f, call(function(s:c.hash_fun), [f, a:cache.seed]), + \ 'ANS_ALWAYS') endf augroup LOCAL_VIMRC From d01fb647468dfc4b9eb2050a56a562c6831aa52b Mon Sep 17 00:00:00 2001 From: Eric Siegerman Date: Mon, 12 May 2014 21:13:29 -0400 Subject: [PATCH 3/6] Version the cache-file format. --- plugin/localvimrc.vim | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugin/localvimrc.vim b/plugin/localvimrc.vim index 94c8368..d81c9e2 100644 --- a/plugin/localvimrc.vim +++ b/plugin/localvimrc.vim @@ -11,6 +11,7 @@ let s:c.cache_file = get(s:c,'cache_file', $HOME.'/.vim_local_rc_cache') let s:c.resource_on_cwd_change = get(s:c, 'resource_on_cwd_change', 1) let s:last_cwd = '' let s:answer_map = ['ANS_GOK', 'ANS_YES', 'ANS_NO', 'ANS_ALWAYS', 'ANS_NEVER'] +let s:cache_format_version=2 " Increment this on any format change " very simple hash function using md5 falling back to VimL implementation fun! LVRHashOfFile(file, seed) @@ -77,8 +78,16 @@ fun! LVRWithCache(F, args) " harder to find collisions let cache = filereadable(s:c.cache_file) \ ? eval(readfile(s:c.cache_file)[0]) - \ : {'seed':localtime()} + \ : {} let c = copy(cache) + " if the cache isn't in the format we understand, just blow it away; + " it's not valuable enough to be worth converting. + " note that we do this whether the file's version is too low or too high; + " in either case, we assume that we don't know how to interpret the contents. + let ver = get(cache, 'format_version', -1) " default should never match any real version number + if ver != s:cache_format_version + let cache = {'seed' : localtime(), 'format_version' : s:cache_format_version} + endif let r = call(a:F, [cache]+a:args) if c != cache | call writefile([string(cache)], s:c.cache_file) | endif return r From 79b90cb779ce9c3687d54fd88a18d728c0c9d195 Mon Sep 17 00:00:00 2001 From: Eric Siegerman Date: Mon, 12 May 2014 22:03:17 -0400 Subject: [PATCH 4/6] Save ANS_ASK status explicitly ... instead of implicitly by not saving the cache entry at all. --- plugin/localvimrc.vim | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plugin/localvimrc.vim b/plugin/localvimrc.vim index d81c9e2..908efc1 100644 --- a/plugin/localvimrc.vim +++ b/plugin/localvimrc.vim @@ -41,15 +41,12 @@ endfun fun! LVRSaveAnswer(cache, key, hash, ans) if 'ANS_ALWAYS' == a:ans || 'ANS_NEVER' == a:ans - let ce = {'hash': a:hash, 'ans': a:ans} - let a:cache[a:key] = ce + let l:ans = a:ans else - " user doesn't want answer saved; delete the cache entry entirely - " N.B.: "unlet!" doesn't suppress missing-key error :-(, so instead - " we make sure the entry is defined before undefining it - let a:cache[a:key] = {} - unlet a:cache[a:key] + let l:ans = 'ANS_ASK' endif + let ce = {'hash': a:hash, 'ans': l:ans} + let a:cache[a:key] = ce endf " source local vimrc, ask user for confirmation if file contents change @@ -61,7 +58,7 @@ fun! LVRSource(file, cache) let h = call(function(s:c.hash_fun), [a:file, a:cache.seed]) " if hash doesn't match or no hash exists ask user to confirm sourcing this file let ce = get(a:cache, p, {'hash':'no-hash', 'ans':'ANS_NO'}) - if ce['hash'] == h + if ce['hash'] == h && ce['ans'] != 'ANS_ASK' let ans = ce['ans'] else let ans = LVRAsk('source '.p,'ANS_NO') From eea86a9f23a06384598f4853d230eccdecb6a26e Mon Sep 17 00:00:00 2001 From: Eric Siegerman Date: Mon, 12 May 2014 22:25:17 -0400 Subject: [PATCH 5/6] Make the user's options backward compatible The "Yes" option is back to meaning "read it from now on"; "read it this time only" is now "Once". --- README.md | 9 ++++----- plugin/localvimrc.vim | 26 ++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fc4a16b..a127487 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,13 @@ Features: remembers your answer for the future. Possible answers are (with the abbreviation captitalized): - - `Yes`: Read the file this time, but do not remember the answer; - you will be asked again next time + - *Yes*: Read the file from now on, without asking - - `No`: Don't read it, and don't remember the answer + - `*Once*: Read the file this time, but ask again next time - - `Always`: Read the file this time, and automatically do so from now on + - `*No*: Don't read it this time, but ask again next time - - `neVer`: Don't read it, and don't ask again in future + - `*neVer*: Don't read it, now or ever - It also remembers a hash of the file's contents, so that if the file changes, you'll be asked again. diff --git a/plugin/localvimrc.vim b/plugin/localvimrc.vim index 908efc1..e9647ea 100644 --- a/plugin/localvimrc.vim +++ b/plugin/localvimrc.vim @@ -10,9 +10,27 @@ let s:c.hash_fun = get(s:c,'hash_fun','LVRHashOfFile') let s:c.cache_file = get(s:c,'cache_file', $HOME.'/.vim_local_rc_cache') let s:c.resource_on_cwd_change = get(s:c, 'resource_on_cwd_change', 1) let s:last_cwd = '' -let s:answer_map = ['ANS_GOK', 'ANS_YES', 'ANS_NO', 'ANS_ALWAYS', 'ANS_NEVER'] let s:cache_format_version=2 " Increment this on any format change +" read ask again +" Value file next time [1] +" ----- ---- ------------- +" ANS_YES yes no +" ANS_ONCE yes yes +" ANS_NO no yes +" ANS_NEVER no no +" ANS_ASK[2] (n/a) yes +" +" [1] Yes: the next time this file is a candidate to be run, the user +" will be asked again whether to actually run it +" No: User's answer will be cached, and applied automatically in +" future (until the file's hash changes) +" +" [2] ANS_ASK is used internally; it doesn't correspond directly to +" any one of the options offered to the user + +let s:answer_map = ['ANS_NO', 'ANS_YES', 'ANS_ONCE', 'ANS_NO', 'ANS_NEVER'] + " very simple hash function using md5 falling back to VimL implementation fun! LVRHashOfFile(file, seed) if executable('md5sum') @@ -31,7 +49,7 @@ endfun " Ask the user; return one of the ANS_* strings. " If they don't provide a useful answer, return dflt. fun! LVRAsk(prompt, dflt) - let ans = confirm(a:prompt,"&Yes\n&No\n&Always\nNe&Ver", a:dflt) + let ans = confirm(a:prompt,"&Yes\n&Once\n&No\nne&Ver", a:dflt) if 0 < ans return s:answer_map[ans] else @@ -40,7 +58,7 @@ fun! LVRAsk(prompt, dflt) endfun fun! LVRSaveAnswer(cache, key, hash, ans) - if 'ANS_ALWAYS' == a:ans || 'ANS_NEVER' == a:ans + if 'ANS_YES' == a:ans || 'ANS_NEVER' == a:ans let l:ans = a:ans else let l:ans = 'ANS_ASK' @@ -64,7 +82,7 @@ fun! LVRSource(file, cache) let ans = LVRAsk('source '.p,'ANS_NO') endif " source the file if so requested - if 'ANS_YES' == ans || 'ANS_ALWAYS' == ans + if 'ANS_YES' == ans || 'ANS_ONCE' == ans exec 'source '.fnameescape(p) endif call LVRSaveAnswer(a:cache, p, h, ans) From 3f47ab8769c4cfec273535702bddad51afba28b5 Mon Sep 17 00:00:00 2001 From: Eric Siegerman Date: Wed, 14 May 2014 00:17:53 -0400 Subject: [PATCH 6/6] Doc improvements --- README.md | 39 ++++++++++++++++++++------------------- plugin/localvimrc.vim | 27 ++++++++------------------- 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index a127487..8706e30 100644 --- a/README.md +++ b/README.md @@ -10,25 +10,25 @@ Features: - When Vim starts up, every directory from root to the directory of the file is traversed and special files such as .(local-)vimrc files are sourced -- Because you don't want to run untrusted code by accident, this plugin - calculates a asks you before sourcing any given file, and optionally - remembers your answer for the future. Possible answers are (with the - abbreviation captitalized): - - - *Yes*: Read the file from now on, without asking - - - `*Once*: Read the file this time, but ask again next time - - - `*No*: Don't read it this time, but ask again next time - - - `*neVer*: Don't read it, now or ever - -- It also remembers a hash of the file's contents, so that if the file - changes, you'll be asked again. - - If you use vim to edit a local vimrc file, an answer of "Always" is - automatically saved for (that version of) the file, on the theory - that you trust any edits you yourself have made. +- Because you don't want to run untrusted code by accident, this plugin asks + you before sourcing any given file. Your answer can optionally be "sticky", + i.e. saved for use in the future. The possible answers (the abbreviation + is captitalized) are: + + Answer | Read now? | Sticky? + :-----:|:---------:|:------: + Yes | Yes | Yes + Once | Yes | No + No | No | No + neVer | No | Yes + +- The plugin also stores a hash of the file's contents; if the file changes, + you'll be asked again, i.e. any sticky answer you gave previously will be + ignored + +- If you use vim to edit a local vimrc file, a sticky "yes" is automatically + saved for (that version of) the file, on the assumption that you trust any + edits you yourself have made - if you change a directory and edit a file the local vimrc files are resourced @@ -72,3 +72,4 @@ unless the file belongs to a different owner.. contributors ============ Thiago de Arruda (github.com/tarruba) +Eric Siegerman (github.com/esiegerman) diff --git a/plugin/localvimrc.vim b/plugin/localvimrc.vim index e9647ea..a41a972 100644 --- a/plugin/localvimrc.vim +++ b/plugin/localvimrc.vim @@ -10,25 +10,14 @@ let s:c.hash_fun = get(s:c,'hash_fun','LVRHashOfFile') let s:c.cache_file = get(s:c,'cache_file', $HOME.'/.vim_local_rc_cache') let s:c.resource_on_cwd_change = get(s:c, 'resource_on_cwd_change', 1) let s:last_cwd = '' -let s:cache_format_version=2 " Increment this on any format change +let s:cache_format_version=2 " Increment this on any format change -" read ask again -" Value file next time [1] -" ----- ---- ------------- -" ANS_YES yes no -" ANS_ONCE yes yes -" ANS_NO no yes -" ANS_NEVER no no -" ANS_ASK[2] (n/a) yes +" Map return values from confirm() into our answer codes. +" If confirm() can't provide a good answer, it returns 0; hence we set +" [0] to a safe default. " -" [1] Yes: the next time this file is a candidate to be run, the user -" will be asked again whether to actually run it -" No: User's answer will be cached, and applied automatically in -" future (until the file's hash changes) -" -" [2] ANS_ASK is used internally; it doesn't correspond directly to -" any one of the options offered to the user - +" Besides these, there's also ANS_ASK, which is only used internally; +" it's written to the cache if the user gives a non-sticky answer. let s:answer_map = ['ANS_NO', 'ANS_YES', 'ANS_ONCE', 'ANS_NO', 'ANS_NEVER'] " very simple hash function using md5 falling back to VimL implementation @@ -97,9 +86,9 @@ fun! LVRWithCache(F, args) let c = copy(cache) " if the cache isn't in the format we understand, just blow it away; " it's not valuable enough to be worth converting. - " note that we do this whether the file's version is too low or too high; + " note that we do this whether the file's version is too low *or* too high; " in either case, we assume that we don't know how to interpret the contents. - let ver = get(cache, 'format_version', -1) " default should never match any real version number + let ver = get(cache, 'format_version', -1) " default should never match any real version number if ver != s:cache_format_version let cache = {'seed' : localtime(), 'format_version' : s:cache_format_version} endif