CheckHealth

- Overlay markdown syntax/filetype, don't invent new filetypes/syntaxes.
- migrate s:check_ruby()
- s:indent_after_line1
- Less-verbose output
This commit is contained in:
Justin M. Keyes 2016-08-07 14:16:30 -04:00
parent 2cc523c3af
commit 545e7a4163
6 changed files with 212 additions and 243 deletions

View File

@ -1,15 +1,30 @@
" Dictionary where we keep all of the healtch check functions we've found.
" Dictionary of all health check functions we have found.
" They will only be run if the value is true
let g:health_checkers = get(g:, 'health_checkers', {})
let s:current_checker = get(s:, 'current_checker', '')
""
" Function to run the health checkers
" It manages the output and any file local settings
function! health#check(bang) abort
let l:report = '# Checking health'
function! s:enhance_syntax() abort
syntax keyword healthError ERROR
highlight link healthError Error
if g:health_checkers == {}
syntax keyword healthWarning WARNING
highlight link healthWarning WarningMsg
syntax keyword healthInfo INFO
highlight link healthInfo ModeMsg
syntax keyword healthSuccess SUCCESS
highlight link healthSuccess Function
syntax keyword healthSuggestion SUGGESTION
highlight link healthSuggestion String
endfunction
" Runs the health checkers. Manages the output and buffer-local settings.
function! health#check(bang) abort
let l:report = ''
if empty(g:health_checkers)
call health#add_checker(s:_default_checkers())
endif
@ -17,20 +32,18 @@ function! health#check(bang) abort
" Disabled checkers will not run their registered check functions
if l:checker[1]
let s:current_checker = l:checker[0]
let l:report .= "\n\n--------------------------------------------------------------------------------\n"
let l:report .= printf("\n## Checker %s says:\n", s:current_checker)
let l:report .= printf("\n%s\n================================================================================",
\ s:current_checker)
let l:report .= capture('call ' . l:checker[0] . '()')
let l:report .= execute('call ' . l:checker[0] . '()')
endif
endfor
let l:report .= "\n--------------------------------------------------------------------------------\n"
if a:bang
new
setlocal bufhidden=wipe
set syntax=health
set filetype=health
set filetype=markdown
call s:enhance_syntax()
call setline(1, split(report, "\n"))
setlocal nomodified
else
@ -43,80 +56,76 @@ function! health#check(bang) abort
endif
endfunction
" Report functions {{{
""
" Start a report section.
" It should represent a general area of tests that can be understood
" from the argument {name}
" To start a new report section, use this function again
" Starts a new report.
function! health#report_start(name) abort " {{{
echo ' - Checking: ' . a:name
echo "\n## " . a:name
endfunction " }}}
""
" Indents lines *except* line 1 of a string if it contains newlines.
function! s:indent_after_line1(s, columns) abort
let lines = split(a:s, "\n", 0)
if len(lines) < 2 " We do not indent line 1, so nothing to do.
return a:s
endif
for i in range(1, len(lines)-1) " Indent lines after the first.
let lines[i] = substitute(lines[i], '^\s*', repeat(' ', a:columns), 'g')
endfor
return join(lines, "\n")
endfunction
" Format a message for a specific report item
function! s:format_report_message(status, msg, ...) abort " {{{
let l:output = ' - ' . a:status . ': ' . a:msg
let output = ' - ' . a:status . ': ' . s:indent_after_line1(a:msg, 4)
let suggestions = []
" Check optional parameters
" Optional parameters
if a:0 > 0
" Suggestions go in the first optional parameter can be a string or list
if type(a:1) == type("")
let l:output .= "\n - SUGGESTIONS:"
let l:output .= "\n - " . a:1
elseif type(a:1) == type([])
" Report each suggestion
let l:output .= "\n - SUGGESTIONS:"
for l:suggestion in a:1
let l:output .= "\n - " . l:suggestion
endfor
else
echoerr "A string or list is required as the optional argument for suggestions"
let suggestions = type(a:1) == type("") ? [a:1] : a:1
if type(suggestions) != type([])
echoerr "Expected String or List"
endif
endif
" Report each suggestion
if len(suggestions) > 0
let output .= "\n - SUGGESTIONS:"
endif
for suggestion in suggestions
let output .= "\n - " . s:indent_after_line1(suggestion, 10)
endfor
return output
endfunction " }}}
""
" Use {msg} to report information in the current section
function! health#report_info(msg) abort " {{{
echo s:format_report_message('INFO', a:msg)
endfunction " }}}
""
" Use {msg} to represent the check that has passed
function! health#report_ok(msg) abort " {{{
echo s:format_report_message('SUCCESS', a:msg)
endfunction " }}}
""
" Use {msg} to represent a failed health check and optionally a list of suggestions on how to fix it.
function! health#report_warn(msg, ...) abort " {{{
if a:0 > 0 && type(a:1) == type([])
if a:0 > 0
echo s:format_report_message('WARNING', a:msg, a:1)
else
echo s:format_report_message('WARNING', a:msg)
endif
endfunction " }}}
""
" Use {msg} to represent a critically failed health check and optionally a list of suggestions on how to fix it.
function! health#report_error(msg, ...) abort " {{{
if a:0 > 0 && type(a:1) == type([])
if a:0 > 0
echo s:format_report_message('ERROR', a:msg, a:1)
else
echo s:format_report_message('ERROR', a:msg)
endif
endfunction " }}}
" }}}
" Health checker management {{{
""
" Add a single health checker
" It does not modify any values if the checker already exists
" Adds a health checker. Does nothing if the checker already exists.
function! s:add_single_checker(checker_name) abort " {{{
if has_key(g:health_checkers, a:checker_name)
return
@ -125,25 +134,19 @@ function! s:add_single_checker(checker_name) abort " {{{
endif
endfunction " }}}
""
" Enable a single health checker
" It will modify the values if the checker already exists
" Enables a health checker.
function! s:enable_single_checker(checker_name) abort " {{{
let g:health_checkers[a:checker_name] = v:true
endfunction " }}}
""
" Disable a single health checker
" It will modify the values if the checker already exists
" Disables a health checker.
function! s:disable_single_checker(checker_name) abort " {{{
let g:health_checkers[a:checker_name] = v:false
endfunction " }}}
""
" Add at least one health checker
" {checker_name} can be specified by either a list of strings or a single string.
" It does not modify any values if the checker already exists
" Adds a health checker. `checker_name` can be a list of strings or
" a single string. Does nothing if the checker already exists.
function! health#add_checker(checker_name) abort " {{{
if type(a:checker_name) == type('')
call s:add_single_checker(a:checker_name)
@ -154,9 +157,8 @@ function! health#add_checker(checker_name) abort " {{{
endif
endfunction " }}}
""
" Enable at least one health checker
" {checker_name} can be specified by either a list of strings or a single string.
" Enables a health checker. `checker_name` can be a list of strings or
" a single string.
function! health#enable_checker(checker_name) abort " {{{
if type(a:checker_name) == type('')
call s:enable_single_checker(a:checker_name)
@ -167,9 +169,8 @@ function! health#enable_checker(checker_name) abort " {{{
endif
endfunction " }}}
""
" Disable at least one health checker
" {checker_name} can be specified by either a list of strings or a single string.
" Disables a health checker. `checker_name` can be a list of strings or
" a single string.
function! health#disable_checker(checker_name) abort " {{{
if type(a:checker_name) == type('')
call s:disable_single_checker(a:checker_name)
@ -196,4 +197,3 @@ function! s:_default_checkers() abort " {{{
endfor
return checkers_to_source
endfunction " }}}
" }}}

View File

@ -1,4 +1,3 @@
" Script variables
let s:bad_responses = [
\ 'unable to parse python response',
\ 'unable to parse',
@ -7,8 +6,6 @@ let s:bad_responses = [
\ 'unable to find neovim version'
\ ]
""
" Check if the string is a bad response
function! s:is_bad_response(s) abort
return index(s:bad_responses, a:s) >= 0
endfunction
@ -33,7 +30,6 @@ function! s:version_cmp(a, b) abort
return 0
endfunction
" Fetch the contents of a URL.
function! s:download(url) abort
let content = ''
@ -61,8 +57,7 @@ function! s:download(url) abort
endfunction
" Get the latest Neovim Python client version from PyPI. The result is
" cached.
" Get the latest Neovim Python client version from PyPI. Result is cached.
function! s:latest_pypi_version() abort
if exists('s:pypi_version')
return s:pypi_version
@ -77,8 +72,6 @@ function! s:latest_pypi_version() abort
endif
endfunction
""
" Get version information using the specified interpreter. The interpreter is
" used directly in case breaking changes were introduced since the last time
" Neovim's Python client was updated.
@ -140,7 +133,6 @@ function! s:version_info(python) abort
return [python_version, nvim_version, pypi_version, version_status]
endfunction
" Check the Python interpreter's usability.
function! s:check_bin(bin) abort
if !filereadable(a:bin)
@ -153,9 +145,6 @@ function! s:check_bin(bin) abort
return 1
endfunction
" Load the remote plugin manifest file and check for unregistered plugins
function! s:check_manifest() abort
call health#report_start('Remote Plugins')
@ -217,6 +206,8 @@ endfunction
function! s:check_python(version) abort
call health#report_start('Python ' . a:version . ' provider')
let python_bin_name = 'python'.(a:version == 2 ? '2' : '3')
let pyenv = resolve(exepath('pyenv'))
let pyenv_root = exists('$PYENV_ROOT') ? resolve($PYENV_ROOT) : 'n'
@ -226,8 +217,6 @@ function! s:check_python(version) abort
let python_bin = ''
let python_multiple = []
call health#report_start('Python ' . a:version . ' Configuration')
if exists('g:'.host_prog_var)
call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var)))
endif
@ -236,9 +225,6 @@ function! s:check_python(version) abort
if empty(python_bin_name)
call health#report_warn('No Python interpreter was found with the neovim '
\ . 'module. Using the first available for diagnostics.')
" TODO: Not sure what to do about these errors, or if this is the right
" type.
if !empty(pythonx_errs)
call health#report_warn(pythonx_errs)
endif
@ -256,12 +242,12 @@ function! s:check_python(version) abort
endif
if !empty(pythonx_errs)
call health#report_error('Provier python has reported errors:', pythonx_errs)
call health#report_error('Python provider error', pythonx_errs)
endif
if !empty(python_bin_name) && empty(python_bin) && empty(pythonx_errs)
if !exists('g:'.host_prog_var)
call health#report_warn(printf('"g:%s" is not set. Searching for '
call health#report_info(printf('`g:%s` is not set. Searching for '
\ . '%s in the environment.', host_prog_var, python_bin_name))
endif
@ -372,7 +358,7 @@ function! s:check_python(version) abort
endif
" Diagnostic output
call health#report_info('Executable:' . (empty(python_bin) ? 'Not found' : python_bin))
call health#report_info('Executable: ' . (empty(python_bin) ? 'Not found' : python_bin))
if len(python_multiple)
for path_bin in python_multiple
call health#report_info('Other python executable: ' . path_bin)
@ -389,7 +375,7 @@ function! s:check_python(version) abort
call health#report_warn('Python 3.3+ is recommended.')
endif
call health#report_info('Python Version: ' . pyversion)
call health#report_info('Python'.a:version.' version: ' . pyversion)
call health#report_info(printf('%s-neovim Version: %s', python_bin_name, current))
if s:is_bad_response(current)
@ -415,12 +401,39 @@ function! s:check_python(version) abort
endfunction
function! s:check_ruby() abort
call health#report_start('Ruby provider')
let min_version = "0.2.4"
let ruby_version = systemlist('ruby -v')[0]
let ruby_prog = provider#ruby#Detect()
let suggestions =
\ ['Install or upgrade the neovim RubyGem using `gem install neovim`.']
if empty(ruby_prog)
let ruby_prog = 'not found'
let prog_vers = 'not found'
call health#report_error('Missing Neovim RubyGem', suggestions)
else
silent let prog_vers = systemlist(ruby_prog . ' --version')[0]
if v:shell_error
let prog_vers = 'outdated'
call health#report_warn('Neovim RubyGem is not up-to-date', suggestions)
elseif s:version_cmp(prog_vers, min_version) == -1
let prog_vers .= ' (outdated)'
call health#report_warn('Neovim RubyGem is not up-to-date', suggestions)
else
call health#report_ok('Found Neovim RubyGem')
endif
endif
call health#report_info('Ruby Version: ' . ruby_version)
call health#report_info('Host Executable: ' . ruby_prog)
call health#report_info('Host Version: ' . prog_vers)
endfunction
function! health#nvim#check() abort
silent call s:check_python(2)
silent echo ''
silent call s:check_python(3)
silent echo ''
silent call s:check_manifest()
silent echo ''
call s:check_manifest()
call s:check_python(2)
call s:check_python(3)
call s:check_ruby()
endfunction

View File

@ -1,146 +1,127 @@
*pi_health.txt* Check the status of your Neovim system
*pi_health.txt* Healthcheck framework
Author: TJ DeVries <devries.timothyj@gmail.com>
==============================================================================
1. Contents *health.vim-contents*
1. Contents : |health.vim-contents|
2. Health.vim introduction : |health.vim-intro|
3. Health.vim manual : |health.vim-manual|
3.1 Health.vim commands : |health.vim-commands|
4. Making a new checker : |health.vim-checkers|
1. Introduction |health.vim-intro|
2. Commands and functions |health.vim-manual|
3. Create a healthcheck |health.vim-dev|
==============================================================================
2. Health.vim introduction *health.vim-intro*
Introduction *healthcheck* *health.vim-intro*
Debugging common issues is a time consuming task that many developers would
like to eliminate, and where elimination is impossible, minimize. Many common
questions and difficulties could be answered by a simple check of an
environment variable or a setting that the user has made. However, even with
FAQs and other manuals, it can be difficult to suggest the path a user should
take without knowing some information about their system.
Troubleshooting user configuration problems is a time-consuming task that
developers want to minimize. health.vim provides a simple framework for plugin
authors to hook into, and for users to invoke, to check and report the user's
configuration and environment. Type this command to try it: >
Health.vim aims to solve this problem in two ways for both core and plugin
maintainers.
The way this is done is to provide an interface that users will know to check
first before posting question in the issue tracker, dev channels, etc. This
is similar to how |:help| functions currently. The user experiencing
difficulty can run |:CheckHealth| to view the status of one's system.
The aim of |:CheckHealth| is two-fold.
The first aim is to provide maintainers with an overview of the user's working
environment. This skips large amounts of time where the maintainer must
instruct the user on which steps to take to get debug information, and allows
the maintainer to extend existing health scripts as more helpful debug
information is found.
The second aim is to provide maintainers a way of automating the answering of
frequently encountered question. A common occurrence with Neovim is that the
user has not installed the necessary Python modules to interact with Python
remote plugins. A simple check of whether the Neovim remote plugin is
installed can lead to a suggestion of >
:CheckHealth
<
For example, some users have broken or unusual Python setups, which breaks the
|:python| command. |:CheckHealth| detects several common Python configuration
problems and reports them. If the Neovim Python module is not installed, it
shows a warning: >
You have not installed the Neovim Python module
You might want to try `$ pip install Neovim`
You might want to try `pip install Neovim`
<
With these possibilities, it allows the maintainer of a plugin to spend more
time on active development, rather than trying to spend time on debugging
common issues many times.
Plugin authors are encouraged to add healthchecks, see |health.vim-dev|.
==============================================================================
3. Health.vim manual *health.vim-manual*
Commands and functions *health.vim-manual*
3.1 Commands
------------
Commands
------------------------------------------------------------------------------
*:CheckHealth*
:CheckHealth Run all healthchecks and show the output in a new
tabpage. These healthchecks are included by default:
- python2
- python3
- ruby
- remote plugin
:CheckHealth[!] *:CheckHealth*
Run all health checkers found in g:health_checkers
It will check your setup for common problems that may be keeping a
plugin from functioning correctly. Include the output of this command
in bug reports to help reduce the amount of time it takes to address
your issue. With "!" the output will be placed in a new buffer which
can make it easier to save to a file or copy to the clipboard.
3.2 Functions *health.functions*
-------------
3.2.1 Report Functions *health.report_functions*
----------------------
The |health.report_functions| are used by the plugin maintainer to remove the
hassle of formatting multiple different levels of output. Not only does it
remove the hassle of formatting, but it also provides users with a consistent
interface for viewing the health information about the system.
These functions are also expected to have the capability to produce output in
multiple different formats. For example, if parsing of the results were to be
done by a remote plugin, the results could be output in a valid JSON format
and then the remote plugin could parse the results easily.
health#report_start({name}) *health.funcs.report_start*
Start a report section. It should represent a general area of tests
that can be understood from the argument {name} To start a new report
section, use this function again
health#report_info({msg}) *health.funcs.report_info*
Use {msg} to report information in the current section
health#report_ok({msg}) *health.funcs.report_ok*
Use {msg} to represent the check that has passed
health#report_warn({msg}, ...) *health.funcs.report_warn*
Use {msg} to represent a failed health check and optionally a list of
suggestions on how to fix it.
health#report_error({msg}, ...) *health.funcs.report_error*
Use {msg} to represent a critically failed health check and optionally
a list of suggestions on how to fix it.
3.3 User Functions *health.user_functions*
------------------
health#{my_plug}#check() *health.user_checker*
A user defined function to run all of the checks that are required for
either debugging or suggestion making. An example might be something
like: >
function! health#my_plug#check() abort
silent call s:check_environment_vars()
silent call s:check_python_configuration()
endfunction
:CheckHealth {plugins}
Run healthchecks for one or more plugins. E.g. to run
only the standard Nvim healthcheck: >
:CheckHealth nvim
< To run the healthchecks for the "foo" and "bar" plugins
(assuming these plugins are on your 'runtimepath' and
they have implemented health#foo#check() and
health#bar#check(), respectively): >
:CheckHealth foo bar
<
This function will be found, sourced, and automatically called when
the user invokes |:CheckHealth|.
Functions
------------------------------------------------------------------------------
All output will be captured from the health checker. It is recommended
that the plugin maintainer uses the calls described in
|health.report_functions|. The benefits these functions provide are
described in the same section.
health.vim functions are for creating new healthchecks. They mostly just do
some layout and formatting, to give users a consistent presentation.
health#report_start({name}) *health#report_start*
Starts a new report. Most plugins should call this only once, but if
you want different sections to appear in your report, call this once
per section.
health#report_info({msg}) *health#report_info*
Displays an informational message.
health#report_ok({msg}) *health#report_ok*
Displays a "success" message.
health#report_warn({msg}, [{suggestions}]) *health#report_warn*
Displays a warning. {suggestions} is an optional List of suggestions.
health#report_error({msg}, [{suggestions}]) *health#report_error*
Displays an error. {suggestions} is an optional List of suggestions.
health#{plugin}#check() *health.user_checker*
This is the form of a healthcheck definition. Call the above functions
from this function, then |:CheckHealth| does the rest. Example: >
function! health#my_plug#check() abort
silent call s:check_environment_vars()
silent call s:check_python_configuration()
endfunction
<
The function will be found and called automatically when the user
invokes |:CheckHealth|.
All output will be captured from the healthcheck. Use the
health#report_* functions so that your healthcheck has a format
consistent with the standard healthchecks.
==============================================================================
4. Making a new checker *health.vim-checkers*
Create a healthcheck *health.vim-dev*
Health checkers are the scripts that check the health of the system. Neovim
has built in checkers, which can be found in `runtime/autoload/health/`. To
add a checker for a plugin, add a `health` folder in the `autoload` directory
of your plugin. It is then suggested that the name of your script be
`{plug_name}.vim`. For example, the health checker for `my_plug` might be
placed in: >
Healthchecks are functions that check the health of the system. Neovim has
built-in checkers, found in $VIMRUNTIME/autoload/health/.
$PLUGIN_BASE/autoload/health/my_plug.vim
>
To add a new checker for your own plugin, simply define a
health#{plugin}#check() function in autoload/health/{plugin}.vim.
|:CheckHealth| automatically finds and invokes such functions.
Inside this script, a function must be specified to run. This function is
described in |health.user_checker|.
If your plugin is named "jslint", then its healthcheck function must be >
health#jslint#check()
<
defined in this file on 'runtimepath': >
autoload/health/jslint.vim
<
Here's a sample to get started: >
function! health#jslint#check() abort
call health#report_start('sanity checks')
" perform arbitrary checks
" ...
if looks_good
call health#report_ok('found required dependencies')
else
call health#report_error('cannot find jslint',
\ ['npm install --save jslint'])
endif
endfunction
<
==============================================================================
vim:tw=78:ts=8:ft=help:fdm=marker

View File

@ -1,3 +1 @@
" call health#add_checker('health#nvim#check')
command! -bang CheckHealth call health#check(<bang>0)

View File

@ -1,20 +0,0 @@
if exists("b:current_syntax")
finish
endif
syntax keyword healthError ERROR
highlight link healthError Error
syntax keyword healthWarning WARNING
highlight link healthWarning Todo
syntax keyword healthInfo INFO
highlight link healthInfo Identifier
syntax keyword healthSuccess SUCCESS
highlight link healthSuccess Function
syntax keyword healthSuggestion SUGGESTION
highlight link healthSuggestion String
let b:current_syntax = "health"

View File

@ -6,7 +6,7 @@ describe('health.vim', function()
plugin_helpers.reset()
end)
it('should echo the results when using the basic functions', function()
it('reports results', function()
helpers.execute("call health#report_start('Foo')")
local report = helpers.redir_exec([[call health#report_start('Check Bar')]])
.. helpers.redir_exec([[call health#report_ok('Bar status')]])
@ -30,25 +30,22 @@ describe('health.vim', function()
end)
describe('CheckHealth', function()
-- Run the health check and store important results
-- Run it here because it may take awhile to complete, depending on the system
describe(':CheckHealth', function()
-- Run it here because it may be slow, depending on the system.
helpers.execute([[CheckHealth!]])
local report = helpers.curbuf_contents()
local health_checkers = helpers.redir_exec("echo g:health_checkers")
it('should find the default checker upon execution', function()
it('finds the default checker', function()
assert(string.find(health_checkers, "'health#nvim#check': v:true"))
end)
it('should alert the user that health#nvim#check is running', function()
assert(string.find(report, '# Checking health'))
assert(string.find(report, 'Checker health#nvim#check says:'))
assert(string.find(report, 'Checking:'))
it('prints a header with the name of the checker', function()
assert(string.find(report, 'health#nvim#check'))
end)
end)
it('should allow users to disable checkers', function()
it('allows users to disable checkers', function()
helpers.execute("call health#disable_checker('health#nvim#check')")
helpers.execute("CheckHealth!")
local health_checkers = helpers.redir_exec("echo g:health_checkers")