274 lines
10 KiB
VimL
274 lines
10 KiB
VimL
|
" autoload/editorconfig_core/ini.vim: Config-file parser for
|
||
|
" editorconfig-core-vimscript and editorconfig-vim.
|
||
|
" Modifed from the Python core's ini.py.
|
||
|
|
||
|
" Copyright (c) 2012-2019 EditorConfig Team {{{2
|
||
|
" All rights reserved.
|
||
|
"
|
||
|
" Redistribution and use in source and binary forms, with or without
|
||
|
" modification, are permitted provided that the following conditions are met:
|
||
|
"
|
||
|
" 1. Redistributions of source code must retain the above copyright notice,
|
||
|
" this list of conditions and the following disclaimer.
|
||
|
" 2. Redistributions in binary form must reproduce the above copyright notice,
|
||
|
" this list of conditions and the following disclaimer in the documentation
|
||
|
" and/or other materials provided with the distribution.
|
||
|
"
|
||
|
" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||
|
" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
|
" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
|
" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
|
" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||
|
" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
|
" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
|
" POSSIBILITY OF SUCH DAMAGE. }}}2
|
||
|
|
||
|
let s:saved_cpo = &cpo
|
||
|
set cpo&vim
|
||
|
|
||
|
" variables {{{2
|
||
|
if !exists('g:editorconfig_core_vimscript_debug')
|
||
|
let g:editorconfig_core_vimscript_debug = 0
|
||
|
endif
|
||
|
" }}}2
|
||
|
" === Constants, including regexes ====================================== {{{2
|
||
|
" Regular expressions for parsing section headers and options.
|
||
|
" Allow ``]`` and escaped ``;`` and ``#`` characters in section headers.
|
||
|
" In fact, allow \ to escape any single character - it needs to cover at
|
||
|
" least \ * ? [ ! ] { }.
|
||
|
unlockvar s:SECTCRE s:OPTCRE s:MAX_SECTION_NAME s:MAX_PROPERTY_NAME s:MAX_PROPERTY_VALUE
|
||
|
let s:SECTCRE = '\v^\s*\[(%([^\\#;]|\\.)+)\]'
|
||
|
|
||
|
" Regular expression for parsing option name/values.
|
||
|
" Allow any amount of whitespaces, followed by separator
|
||
|
" (either ``:`` or ``=``), followed by any amount of whitespace and then
|
||
|
" any characters to eol
|
||
|
let s:OPTCRE = '\v\s*([^:=[:space:]][^:=]*)\s*([:=])\s*(.*)$'
|
||
|
|
||
|
let s:MAX_SECTION_NAME = 4096
|
||
|
let s:MAX_PROPERTY_NAME = 50
|
||
|
let s:MAX_PROPERTY_VALUE = 255
|
||
|
|
||
|
lockvar s:SECTCRE s:OPTCRE s:MAX_SECTION_NAME s:MAX_PROPERTY_NAME s:MAX_PROPERTY_VALUE
|
||
|
|
||
|
" }}}2
|
||
|
" === Main ============================================================== {{{1
|
||
|
|
||
|
" Read \p config_filename and return the options applicable to
|
||
|
" \p target_filename. This is the main entry point in this file.
|
||
|
function! editorconfig_core#ini#read_ini_file(config_filename, target_filename)
|
||
|
let l:oldenc = &encoding
|
||
|
|
||
|
if !filereadable(a:config_filename)
|
||
|
return {}
|
||
|
endif
|
||
|
|
||
|
try " so &encoding will always be reset
|
||
|
let &encoding = 'utf-8' " so readfile() will strip BOM
|
||
|
let l:lines = readfile(a:config_filename)
|
||
|
let result = s:parse(a:config_filename, a:target_filename, l:lines)
|
||
|
catch
|
||
|
let &encoding = l:oldenc
|
||
|
" rethrow, but with a prefix since throw 'Vim...' fails.
|
||
|
throw 'Could not read editorconfig file at ' . v:throwpoint . ': ' . string(v:exception)
|
||
|
endtry
|
||
|
|
||
|
let &encoding = l:oldenc
|
||
|
return result
|
||
|
endfunction
|
||
|
|
||
|
function! s:parse(config_filename, target_filename, lines)
|
||
|
" Parse a sectioned setup file.
|
||
|
" The sections in setup file contains a title line at the top,
|
||
|
" indicated by a name in square brackets (`[]'), plus key/value
|
||
|
" options lines, indicated by `name: value' format lines.
|
||
|
" Continuations are represented by an embedded newline then
|
||
|
" leading whitespace. Blank lines, lines beginning with a '#',
|
||
|
" and just about everything else are ignored.
|
||
|
|
||
|
let l:in_section = 0
|
||
|
let l:matching_section = 0
|
||
|
let l:optname = ''
|
||
|
let l:lineno = 0
|
||
|
let l:e = [] " Errors, if any
|
||
|
|
||
|
let l:options = {} " Options applicable to this file
|
||
|
let l:is_root = 0 " Whether a:config_filename declares root=true
|
||
|
|
||
|
while 1
|
||
|
if l:lineno == len(a:lines)
|
||
|
break
|
||
|
endif
|
||
|
|
||
|
let l:line = a:lines[l:lineno]
|
||
|
let l:lineno = l:lineno + 1
|
||
|
|
||
|
" comment or blank line?
|
||
|
if editorconfig_core#util#strip(l:line) ==# ''
|
||
|
continue
|
||
|
endif
|
||
|
if l:line =~# '\v^[#;]'
|
||
|
continue
|
||
|
endif
|
||
|
|
||
|
" is it a section header?
|
||
|
if g:editorconfig_core_vimscript_debug
|
||
|
echom "Header? <" . l:line . ">"
|
||
|
endif
|
||
|
|
||
|
let l:mo = matchlist(l:line, s:SECTCRE)
|
||
|
if len(l:mo)
|
||
|
let l:sectname = l:mo[1]
|
||
|
let l:in_section = 1
|
||
|
if strlen(l:sectname) > s:MAX_SECTION_NAME
|
||
|
" Section name too long => ignore the section
|
||
|
let l:matching_section = 0
|
||
|
else
|
||
|
let l:matching_section = s:matches_filename(
|
||
|
\ a:config_filename, a:target_filename, l:sectname)
|
||
|
endif
|
||
|
|
||
|
if g:editorconfig_core_vimscript_debug
|
||
|
echom 'In section ' . l:sectname . ', which ' .
|
||
|
\ (l:matching_section ? 'matches' : 'does not match')
|
||
|
\ ' file ' . a:target_filename . ' (config ' .
|
||
|
\ a:config_filename . ')'
|
||
|
endif
|
||
|
|
||
|
" So sections can't start with a continuation line
|
||
|
let l:optname = ''
|
||
|
|
||
|
" Is it an option line?
|
||
|
else
|
||
|
let l:mo = matchlist(l:line, s:OPTCRE)
|
||
|
if len(l:mo)
|
||
|
let l:optname = mo[1]
|
||
|
let l:optval = mo[3]
|
||
|
|
||
|
if g:editorconfig_core_vimscript_debug
|
||
|
echom printf('Saw raw opt <%s>=<%s>', l:optname, l:optval)
|
||
|
endif
|
||
|
|
||
|
if l:optval =~# '\v[;#]'
|
||
|
" ';' and '#' are comment delimiters only if
|
||
|
" preceded by a spacing character
|
||
|
let l:m = matchlist(l:optval, '\v(.{-})\s[;#]')
|
||
|
if len(l:m)
|
||
|
let l:optval = l:m[1]
|
||
|
endif
|
||
|
|
||
|
" ; and # can be escaped with backslash.
|
||
|
let l:optval = substitute(l:optval, '\v\\([;#])', '\1', 'g')
|
||
|
|
||
|
endif
|
||
|
let l:optval = editorconfig_core#util#strip(l:optval)
|
||
|
" allow empty values
|
||
|
if l:optval ==? '""'
|
||
|
let l:optval = ''
|
||
|
endif
|
||
|
let l:optname = s:optionxform(l:optname)
|
||
|
if !l:in_section && optname ==? 'root'
|
||
|
let l:is_root = (optval ==? 'true')
|
||
|
endif
|
||
|
if g:editorconfig_core_vimscript_debug
|
||
|
echom printf('Saw opt <%s>=<%s>', l:optname, l:optval)
|
||
|
endif
|
||
|
|
||
|
if l:matching_section &&
|
||
|
\ strlen(l:optname) <= s:MAX_PROPERTY_NAME &&
|
||
|
\ strlen(l:optval) <= s:MAX_PROPERTY_VALUE
|
||
|
let l:options[l:optname] = l:optval
|
||
|
endif
|
||
|
else
|
||
|
" a non-fatal parsing error occurred. set up the
|
||
|
" exception but keep going. the exception will be
|
||
|
" raised at the end of the file and will contain a
|
||
|
" list of all bogus lines
|
||
|
call add(e, "Parse error in '" . a:config_filename . "' at line " .
|
||
|
\ l:lineno . ": '" . l:line . "'")
|
||
|
endif
|
||
|
endif
|
||
|
endwhile
|
||
|
|
||
|
" if any parsing errors occurred, raise an exception
|
||
|
if len(l:e)
|
||
|
throw string(l:e)
|
||
|
endif
|
||
|
|
||
|
return {'root': l:is_root, 'options': l:options}
|
||
|
endfunction!
|
||
|
|
||
|
" }}}1
|
||
|
" === Helpers =========================================================== {{{1
|
||
|
|
||
|
" Preprocess option names
|
||
|
function! s:optionxform(optionstr)
|
||
|
let l:result = substitute(a:optionstr, '\v\s+$', '', 'g') " rstrip
|
||
|
return tolower(l:result)
|
||
|
endfunction
|
||
|
|
||
|
" Return true if \p glob matches \p target_filename
|
||
|
function! s:matches_filename(config_filename, target_filename, glob)
|
||
|
" config_dirname = normpath(dirname(config_filename)).replace(sep, '/')
|
||
|
let l:config_dirname = fnamemodify(a:config_filename, ':p:h') . '/'
|
||
|
|
||
|
if editorconfig_core#util#is_win()
|
||
|
" Regardless of whether shellslash is set, make everything slashes
|
||
|
let l:config_dirname =
|
||
|
\ tolower(substitute(l:config_dirname, '\v\\', '/', 'g'))
|
||
|
endif
|
||
|
|
||
|
let l:glob = substitute(a:glob, '\v\\([#;])', '\1', 'g')
|
||
|
|
||
|
" Take account of the path to the editorconfig file.
|
||
|
" editorconfig-core-c/src/lib/editorconfig.c says:
|
||
|
" "Pattern would be: /dir/of/editorconfig/file[double_star]/[section] if
|
||
|
" section does not contain '/', or /dir/of/editorconfig/file[section]
|
||
|
" if section starts with a '/', or /dir/of/editorconfig/file/[section] if
|
||
|
" section contains '/' but does not start with '/'."
|
||
|
|
||
|
if stridx(l:glob, '/') != -1 " contains a slash
|
||
|
if l:glob[0] ==# '/'
|
||
|
let l:glob = l:glob[1:] " trim leading slash
|
||
|
endif
|
||
|
" This will be done by fnmatch
|
||
|
" let l:glob = l:config_dirname . l:glob
|
||
|
else " does not contain a slash
|
||
|
let l:config_dirname = l:config_dirname[:-2]
|
||
|
" Trim trailing slash
|
||
|
let l:glob = '**/' . l:glob
|
||
|
endif
|
||
|
|
||
|
if g:editorconfig_core_vimscript_debug
|
||
|
echom '- ini#matches_filename: checking <' . a:target_filename .
|
||
|
\ '> against <' . l:glob . '> with respect to config file <' .
|
||
|
\ a:config_filename . '>'
|
||
|
echom '- ini#matches_filename: config_dirname is ' . l:config_dirname
|
||
|
endif
|
||
|
|
||
|
return editorconfig_core#fnmatch#fnmatch(a:target_filename,
|
||
|
\ l:config_dirname, l:glob)
|
||
|
endfunction " matches_filename
|
||
|
|
||
|
" }}}1
|
||
|
" === Copyright notices ================================================= {{{2
|
||
|
" Based on code from ConfigParser.py file distributed with Python 2.6.
|
||
|
" Portions Copyright (c) 2001-2010 Python Software Foundation;
|
||
|
" All Rights Reserved. Licensed under PSF License (see LICENSE.PSF file).
|
||
|
"
|
||
|
" Changes to original ConfigParser:
|
||
|
"
|
||
|
" - Special characters can be used in section names
|
||
|
" - Octothorpe can be used for comments (not just at beginning of line)
|
||
|
" - Only track INI options in sections that match target filename
|
||
|
" - Stop parsing files with when ``root = true`` is found
|
||
|
" }}}2
|
||
|
|
||
|
let &cpo = s:saved_cpo
|
||
|
unlet! s:saved_cpo
|
||
|
|
||
|
" vi: set fdm=marker fdl=1:
|