Add support for temporary filename substitution, for replacing stdin_wrapper

This commit is contained in:
w0rp 2017-02-11 18:14:18 +00:00
parent 88192e8662
commit 03ab963d1a
4 changed files with 176 additions and 17 deletions

View File

@ -278,6 +278,66 @@ function! s:FixLocList(buffer, loclist) abort
endfor endfor
endfunction endfunction
" Given part of a command, replace any % with %%, so that no characters in
" the string will be replaced with filenames, etc.
function! ale#engine#EscapeCommandPart(command_part) abort
return substitute(a:command_part, '%', '%%', 'g')
endfunction
" Given a command string, replace every...
" %s -> with the current filename
" %t -> with the name of an unused file in a temporary directory
" %% -> with a literal %
function! ale#engine#FormatCommand(buffer, command) abort
let l:temporary_file = ''
let l:command = a:command
" First replace all uses of %%, used for literal percent characters,
" with an ugly string.
let l:command = substitute(l:command, '%%', '<<PERCENTS>>', 'g')
" Replace all %s occurences in the string with the name of the current
" file.
if l:command =~# '%s'
let l:filename = fnamemodify(bufname(a:buffer), ':p')
let l:command = substitute(l:command, '%s', fnameescape(l:filename), 'g')
endif
if l:command =~# '%t'
" Create a temporary filename, <temp_dir>/<original_basename>
" The file itself will not be created by this function.
let l:temporary_file =
\ tempname()
\ . (has('win32') ? '\' : '/')
\ . fnamemodify(bufname(a:buffer), ':t')
let l:command = substitute(l:command, '%t', fnameescape(l:temporary_file), 'g')
endif
" Finish formatting so %% becomes %.
let l:command = substitute(l:command, '<<PERCENTS>>', '%', 'g')
return [l:temporary_file, l:command]
endfunction
function! s:CreateTemporaryFileForJob(buffer, temporary_file) abort
if empty(a:temporary_file)
" There is no file, so we didn't create anything.
return 0
endif
let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
" Create the temporary directory for the file, unreadable by 'other'
" users.
call mkdir(l:temporary_directory, '', 0750)
" Automatically delete the directory later.
call ale#engine#ManageDirectory(a:buffer, l:temporary_directory)
" Write the buffer out to a file.
silent! exec 'write' a:temporary_file
return 1
endfunction
function! s:RunJob(options) abort function! s:RunJob(options) abort
let l:command = a:options.command let l:command = a:options.command
let l:buffer = a:options.buffer let l:buffer = a:options.buffer
@ -286,10 +346,12 @@ function! s:RunJob(options) abort
let l:next_chain_index = a:options.next_chain_index let l:next_chain_index = a:options.next_chain_index
let l:read_buffer = a:options.read_buffer let l:read_buffer = a:options.read_buffer
if l:command =~# '%s' let [l:temporary_file, l:command] = ale#engine#FormatCommand(l:buffer, l:command)
" If there is a '%s' in the command string, replace it with the name
" of the file. if s:CreateTemporaryFileForJob(l:buffer, l:temporary_file)
let l:command = printf(l:command, shellescape(fnamemodify(bufname(l:buffer), ':p'))) " If a temporary filename has been formatted in to the command, then
" we do not need to send the Vim buffer to the command.
let l:read_buffer = 0
endif endif
if has('nvim') if has('nvim')

View File

@ -1030,6 +1030,15 @@ ale#Queue(delay) *ale#Queue()*
again from the same buffer again from the same buffer
ale#engine#EscapeCommandPart(command_part) *ale#engine#EscapeCommandPart()*
Given a |String|, return a |String| with all `%` characters replaced with
`%%` instead. This function can be used to escape strings which are
dynamically generated for commands before handing them over to ALE,
so that ALE doesn't treat any strings with `%` formatting sequences
specially.
ale#engine#GetLoclist(buffer) *ale#engine#GetLoclist()* ale#engine#GetLoclist(buffer) *ale#engine#GetLoclist()*
Given a buffer number, this function will rerurn the list of warnings and Given a buffer number, this function will rerurn the list of warnings and
@ -1186,16 +1195,35 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
See |ale#engine#ManageFile()| and |ale#engine#ManageDirectory| for more See |ale#engine#ManageFile()| and |ale#engine#ManageDirectory| for more
information. information.
Some programs for checking for errors are not capable of receiving input All command strings will be formatted for special character sequences.
from stdin, as is required by ALE. To remedy this, a wrapper script is Any substring `%s` will be replaced with the full path to the current file
provided named in the variable |g:ale#util#stdin_wrapper|. This variable being edited. This format option can be used to pass the exact filename
can be called with the regular arguments for any command to forward data being edited to a program.
from stdin to the program, by way of creating a temporary file. The first
argument to the stdin wrapper must be a file extension to save the temporary
file with, and the following arguments are the command as normal.
For example: > For example: >
'command': g:ale#util#stdin_wrapper . ' .hs ghc -fno-code -v0', 'command': 'eslint -f unix --stdin --stdin-filename %s'
< <
Any substring `%t` will be replaced with a path to a temporary file. Merely
adding `%t` will cause ALE to create a temporary file containing the
contents of the the buffer being checked. All occurrences of `%t` in command
strings will reference the one temporary file. The temporary file will be
created inside a temporary directory, and the entire temporary directory
will be automatically deleted, following the behaviour of
|ale#engine#ManageDirectory|. This option can be used for some linters which
do not support reading from stdin.
For example: >
'command': 'ghc -fno-code -v0 %t',
<
The character sequence `%%` can be used to emit a literal `%` into a
command, so literal character sequences `%s` and `%t` can be escaped by
using `%%s` and `%%t` instead, etc.
If a callback for a command generates part of a command string which might
possibly contain `%%`, `%s`, or `%t` where the special formatting behaviour
is not desired, the |ale#engine#EscapeCommandPart()| function can be used to
replace those characters to avoid formatting issues.
ale#linter#Get(filetype) *ale#linter#Get()* ale#linter#Get(filetype) *ale#linter#Get()*
Return all of linters configured for a given filetype as a |List| of Return all of linters configured for a given filetype as a |List| of
@ -1217,11 +1245,6 @@ ale#statusline#Status() *ale#statusline#Status()*
%{ale#statusline#Status()} %{ale#statusline#Status()}
g:ale#util#stdin_wrapper *g:ale#util#stdin_wrapper*
This variable names a wrapper script for sending stdin input to programs
which cannot accept input via stdin. See |ale#linter#Define()| for more.
ALELint *ALELint* ALELint *ALELint*
This |User| autocommand is triggered by ALE every time it completes a lint This |User| autocommand is triggered by ALE every time it completes a lint
operation. It can be used to update statuslines, send notifications, or operation. It can be used to update statuslines, send notifications, or

View File

@ -0,0 +1,41 @@
Before:
silent! cd /testplugin/test
:e! top/middle/bottom/dummy.txt
After:
unlet! g:result
unlet! g:match
Execute(FormatCommand should do nothing to basic command strings):
AssertEqual ['', 'awesome-linter do something'], ale#engine#FormatCommand(bufnr('%'), 'awesome-linter do something')
Execute(FormatCommand should handle %%, and ignore other percents):
AssertEqual ['', '% %%d %%f %x %'], ale#engine#FormatCommand(bufnr('%'), '%% %%%d %%%f %x %')
Execute(FormatCommand should convert %s to the current filename):
AssertEqual ['', 'foo ' . fnameescape(expand('%:p')) . ' bar ' . fnameescape(expand('%:p'))], ale#engine#FormatCommand(bufnr('%'), 'foo %s bar %s')
Execute(FormatCommand should convert %t to a new temporary filename):
let g:result = ale#engine#FormatCommand(bufnr('%'), 'foo %t bar %t')
let g:match = matchlist(g:result[1], '\v^foo (/tmp/.*/dummy.txt) bar (/tmp/.*/dummy.txt)$')
Assert !empty(g:match), 'No match found! Result was: ' . g:result[1]
" The first item of the result should be a temporary filename, and it should
" be the same as the escaped name in the command string.
AssertEqual g:result[0], fnameescape(g:match[1])
" The two temporary filenames formatted in should be the same.
AssertEqual g:match[1], g:match[2]
Execute(FormatCommand should let you combine %s and %t):
let g:result = ale#engine#FormatCommand(bufnr('%'), 'foo %t bar %s')
let g:match = matchlist(g:result[1], '\v^foo (/tmp/.*/dummy.txt) bar (.*/dummy.txt)$')
Assert !empty(g:match), 'No match found! Result was: ' . g:result[1]
" The first item of the result should be a temporary filename, and it should
" be the same as the escaped name in the command string.
AssertEqual g:result[0], fnameescape(g:match[1])
" The second item should be equal to the original filename.
AssertEqual fnameescape(expand('%:p')), g:match[2]
Execute(EscapeCommandPart should escape all percent signs):
AssertEqual '%%s %%t %%%% %%s %%t %%%%', ale#engine#EscapeCommandPart('%s %t %% %s %t %%')

View File

@ -0,0 +1,33 @@
Before:
let g:output = []
function! TestCallback(buffer, output)
let g:output = a:output
return []
endfunction
call ale#linter#Define('foobar', {
\ 'name': 'testlinter',
\ 'callback': 'TestCallback',
\ 'executable': 'cat',
\ 'command': 'cat %t',
\})
After:
unlet! g:output
delfunction TestCallback
call ale#linter#Reset()
Given foobar (Some imaginary filetype):
foo
bar
baz
Execute(ALE should be able to read the %t file):
AssertEqual 'foobar', &filetype
call ale#Lint()
call ale#engine#WaitForJobs(2000)
AssertEqual ['foo', 'bar', 'baz'], g:output