mirror of
1
0
Fork 0
ultimate-vim/sources_non_forked/editorconfig-vim/autoload/editorconfig_core/fnmatch.vim

466 lines
16 KiB
VimL

" autoload/editorconfig_core/fnmatch.vim: Globbing for
" editorconfig-vim. Ported from the Python core's fnmatch.py.
" Copyright (c) 2012-2019 EditorConfig Team {{{1
" 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. }}}1
"Filename matching with shell patterns.
"
"fnmatch(FILENAME, PATH, PATTERN) matches according to the local convention.
"fnmatchcase(FILENAME, PATH, PATTERN) always takes case in account.
"
"The functions operate by translating the pattern into a regular
"expression. They cache the compiled regular expressions for speed.
"
"The function translate(PATTERN) returns a regular expression
"corresponding to PATTERN. (It does not compile it.)
let s:saved_cpo = &cpo
set cpo&vim
" variables {{{1
if !exists('g:editorconfig_core_vimscript_debug')
let g:editorconfig_core_vimscript_debug = 0
endif
" }}}1
" === Regexes =========================================================== {{{1
let s:LEFT_BRACE = '\v%(^|[^\\])\{'
"LEFT_BRACE = re.compile(
" r"""
"
" (?: ^ | [^\\] ) # Beginning of string or a character besides "\"
"
" \{ # "{"
"
" """, re.VERBOSE
")
let s:RIGHT_BRACE = '\v%(^|[^\\])\}'
"RIGHT_BRACE = re.compile(
" r"""
"
" (?: ^ | [^\\] ) # Beginning of string or a character besides "\"
"
" \} # "}"
"
" """, re.VERBOSE
")
let s:NUMERIC_RANGE = '\v([+-]?\d+)' . '\.\.' . '([+-]?\d+)'
"NUMERIC_RANGE = re.compile(
" r"""
" ( # Capture a number
" [+-] ? # Zero or one "+" or "-" characters
" \d + # One or more digits
" )
"
" \.\. # ".."
"
" ( # Capture a number
" [+-] ? # Zero or one "+" or "-" characters
" \d + # One or more digits
" )
" """, re.VERBOSE
")
" }}}1
" === Internal functions ================================================ {{{1
" Dump the bytes of a:text. For debugging use.
function! s:dump_bytes(text)
let l:idx=0
while l:idx < strlen(a:text)
let l:byte_val = char2nr(a:text[l:idx])
echom printf('%10s%-5d%02x %s', '', l:idx, l:byte_val,
\ a:text[l:idx])
let l:idx+=1
endwhile
endfunction "s:dump_bytes
" Dump the characters of a:text and their codepoints. For debugging use.
function! s:dump_chars(text)
let l:chars = split(a:text, '\zs')
let l:idx = 0
let l:out1 = ''
let l:out2 = ''
while l:idx < len(l:chars)
let l:char = l:chars[l:idx]
let l:out1 .= printf('%5s', l:char)
let l:out2 .= printf('%5x', char2nr(l:char))
let l:idx+=1
endwhile
echom l:out1
echom l:out2
endfunction "s:dump_chars
" }}}1
" === Translating globs to patterns ===================================== {{{1
" Used by s:re_escape: backslash-escape any character below U+0080;
" replace all others with a %U escape.
" See https://vi.stackexchange.com/a/19617/1430 by yours truly
" (https://vi.stackexchange.com/users/1430/cxw).
unlockvar s:replacement_expr
let s:replacement_expr =
\ '\=' .
\ '((char2nr(submatch(1)) >= 128) ? ' .
\ 'printf("%%U%08x", char2nr(submatch(1))) : ' .
\ '("\\" . submatch(1))' .
\ ')'
lockvar s:replacement_expr
" Escaper for very-magic regexes
function! s:re_escape(text)
return substitute(a:text, '\v([^0-9a-zA-Z_])', s:replacement_expr, 'g')
endfunction
"def translate(pat, nested=0):
" Translate a shell PATTERN to a regular expression.
" There is no way to quote meta-characters.
function! editorconfig_core#fnmatch#translate(pat, ...)
let l:nested = 0
if a:0
let l:nested = a:1
endif
if g:editorconfig_core_vimscript_debug
echom '- fnmatch#translate: pattern ' . a:pat
echom printf(
\ '- %d chars', strlen(substitute(a:pat, ".", "x", "g")))
call s:dump_chars(a:pat)
endif
let l:pat = a:pat " TODO remove if we wind up not needing this
" Note: the Python sets MULTILINE and DOTALL, but Vim has \_.
" instead of DOTALL, and \_^ / \_$ instead of MULTILINE.
let l:is_escaped = 0
" Find out whether the pattern has balanced braces.
let l:left_braces=[]
let l:right_braces=[]
call substitute(l:pat, s:LEFT_BRACE, '\=add(l:left_braces, 1)', 'g')
call substitute(l:pat, s:RIGHT_BRACE, '\=add(l:right_braces, 1)', 'g')
" Thanks to http://jeromebelleman.gitlab.io/posts/productivity/vimsub/
let l:matching_braces = (len(l:left_braces) == len(l:right_braces))
" Unicode support (#2). Indexing l:pat[l:index] returns bytes, per
" https://github.com/neovim/neovim/issues/68#issue-28114985 .
" Instead, use split() per vimdoc to break the input string into an
" array of *characters*, and process that.
let l:characters = split(l:pat, '\zs')
let l:index = 0 " character index
let l:length = len(l:characters)
let l:brace_level = 0
let l:in_brackets = 0
let l:result = ''
let l:numeric_groups = []
while l:index < l:length
let l:current_char = l:characters[l:index]
let l:index += 1
" if g:editorconfig_core_vimscript_debug
" echom ' - fnmatch#translate: ' . l:current_char . '@' .
" \ (l:index-1) . '; result ' . l:result
" endif
if l:current_char ==# '*'
let l:pos = l:index
if l:pos < l:length && l:characters[l:pos] ==# '*'
let l:result .= '\_.*'
let l:index += 1 " skip the second star
else
let l:result .= '[^/]*'
endif
elseif l:current_char ==# '?'
let l:result .= '\_[^/]'
elseif l:current_char ==# '['
if l:in_brackets
let l:result .= '\['
else
let l:pos = l:index
let l:has_slash = 0
while l:pos < l:length && l:characters[l:pos] != ']'
if l:characters[l:pos] ==# '/' && l:characters[l:pos-1] !=# '\'
let has_slash = 1
break
endif
let l:pos += 1
endwhile
if l:has_slash
" POSIX IEEE 1003.1-2017 sec. 2.13.3: '/' cannot occur
" in a bracket expression, so [/] matches a literal
" three-character string '[' . '/' . ']'.
let l:result .= '\['
\ . s:re_escape(join(l:characters[l:index : l:pos-1], ''))
\ . '\/'
" escape the slash
let l:index = l:pos + 1
" resume after the slash
else
if l:index < l:length && l:characters[l:index] =~# '\v%(\^|\!)'
let l:index += 1
let l:result .= '[^'
else
let l:result .= '['
endif
let l:in_brackets = 1
endif
endif
elseif l:current_char ==# '-'
if l:in_brackets
let l:result .= l:current_char
else
let l:result .= '\' . l:current_char
endif
elseif l:current_char ==# ']'
if l:in_brackets && !l:is_escaped
let l:result .= ']'
let l:in_brackets = 0
elseif l:is_escaped
let l:result .= '\]'
let l:is_escaped = 0
else
let l:result .= '\]'
endif
elseif l:current_char ==# '{'
let l:pos = l:index
let l:has_comma = 0
while l:pos < l:length && (l:characters[l:pos] !=# '}' || l:is_escaped)
if l:characters[l:pos] ==# ',' && ! l:is_escaped
let l:has_comma = 1
break
endif
let l:is_escaped = l:characters[l:pos] ==# '\' && ! l:is_escaped
let l:pos += 1
endwhile
if ! l:has_comma && l:pos < l:length
let l:num_range =
\ matchlist(join(l:characters[l:index : l:pos-1], ''),
\ s:NUMERIC_RANGE)
if len(l:num_range) > 0 " Remember the ranges
call add(l:numeric_groups, [ 0+l:num_range[1], 0+l:num_range[2] ])
let l:result .= '([+-]?\d+)'
else
let l:inner_xlat = editorconfig_core#fnmatch#translate(
\ join(l:characters[l:index : l:pos-1], ''), 1)
let l:inner_result = l:inner_xlat[0]
let l:inner_groups = l:inner_xlat[1]
let l:result .= '\{' . l:inner_result . '\}'
let l:numeric_groups += l:inner_groups
endif
let l:index = l:pos + 1
elseif l:matching_braces
let l:result .= '%('
let l:brace_level += 1
else
let l:result .= '\{'
endif
elseif l:current_char ==# ','
if l:brace_level > 0 && ! l:is_escaped
let l:result .= '|'
else
let l:result .= '\,'
endif
elseif l:current_char ==# '}'
if l:brace_level > 0 && ! l:is_escaped
let l:result .= ')'
let l:brace_level -= 1
else
let l:result .= '\}'
endif
elseif l:current_char ==# '/'
if join(l:characters[l:index : (l:index + 2)], '') ==# '**/'
let l:result .= '%(/|/\_.*/)'
let l:index += 3
else
let l:result .= '\/'
endif
elseif l:current_char != '\'
let l:result .= s:re_escape(l:current_char)
endif
if l:current_char ==# '\'
if l:is_escaped
let l:result .= s:re_escape(l:current_char)
endif
let l:is_escaped = ! l:is_escaped
else
let l:is_escaped = 0
endif
endwhile
if ! l:nested
let l:result .= '\_$'
endif
return [l:result, l:numeric_groups]
endfunction " #editorconfig_core#fnmatch#translate
let s:_cache = {}
function! s:cached_translate(pat)
if ! has_key(s:_cache, a:pat)
"regex = re.compile(res)
let s:_cache[a:pat] =
\ editorconfig_core#fnmatch#translate(a:pat)
" we don't compile the regex
endif
return s:_cache[a:pat]
endfunction " cached_translate
" }}}1
" === Matching functions ================================================ {{{1
function! editorconfig_core#fnmatch#fnmatch(name, path, pattern)
"def fnmatch(name, pat):
" """Test whether FILENAME matches PATH/PATTERN.
"
" Patterns are Unix shell style:
"
" - ``*`` matches everything except path separator
" - ``**`` matches everything
" - ``?`` matches any single character
" - ``[seq]`` matches any character in seq
" - ``[!seq]`` matches any char not in seq
" - ``{s1,s2,s3}`` matches any of the strings given (separated by commas)
"
" An initial period in FILENAME is not special.
" Both FILENAME and PATTERN are first case-normalized
" if the operating system requires it.
" If you don't want this, use fnmatchcase(FILENAME, PATTERN).
" """
"
" Note: This throws away the backslash in '\.txt' on Cygwin, but that
" makes sense since it's Windows under the hood.
" We don't care about shellslash since we're going to change backslashes
" to slashes in just a moment anyway.
let l:localname = fnamemodify(a:name, ':p')
if editorconfig_core#util#is_win() " normalize
let l:localname = substitute(tolower(l:localname), '\v\\', '/', 'g')
let l:path = substitute(tolower(a:path), '\v\\', '/', 'g')
let l:pattern = tolower(a:pattern)
else
let l:localname = l:localname
let l:path = a:path
let l:pattern = a:pattern
endif
if g:editorconfig_core_vimscript_debug
echom '- fnmatch#fnmatch testing <' . l:localname . '> against <' .
\ l:pattern . '> wrt <' . l:path . '>'
endif
return editorconfig_core#fnmatch#fnmatchcase(l:localname, l:path, l:pattern)
endfunction " fnmatch
function! editorconfig_core#fnmatch#fnmatchcase(name, path, pattern)
"def fnmatchcase(name, pat):
" """Test whether FILENAME matches PATH/PATTERN, including case.
"
" This is a version of fnmatch() which doesn't case-normalize
" its arguments.
" """
"
let [regex, num_groups] = s:cached_translate(a:pattern)
let l:escaped_path = s:re_escape(a:path)
let l:regex = '\v' . l:escaped_path . l:regex
if g:editorconfig_core_vimscript_debug
echom '- fnmatch#fnmatchcase: regex ' . l:regex
call s:dump_chars(l:regex)
echom '- fnmatch#fnmatchcase: checking ' . a:name
call s:dump_chars(a:name)
endif
let l:match_groups = matchlist(a:name, l:regex)[1:] " [0] = full match
if g:editorconfig_core_vimscript_debug
echom printf(' Got %d matches', len(l:match_groups))
endif
if len(l:match_groups) == 0
return 0
endif
" Check numeric ranges
let pattern_matched = 1
for l:idx in range(0,len(l:match_groups))
let l:num = l:match_groups[l:idx]
if l:num ==# ''
break
endif
let [min_num, max_num] = num_groups[l:idx]
if (min_num > (0+l:num)) || ((0+l:num) > max_num)
let pattern_matched = 0
break
endif
" Reject leading zeros without sign. This is very odd ---
" see editorconfig/editorconfig#371.
if match(l:num, '\v^0') != -1
let pattern_matched = 0
break
endif
endfor
if g:editorconfig_core_vimscript_debug
echom '- fnmatch#fnmatchcase: ' . (pattern_matched ? 'matched' : 'did not match')
endif
return pattern_matched
endfunction " fnmatchcase
" }}}1
" === Copyright notices ================================================= {{{1
" Based on code from fnmatch.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 fnmatch:
"
" - translate function supports ``*`` and ``**`` similarly to fnmatch C library
" }}}1
let &cpo = s:saved_cpo
unlet! s:saved_cpo
" vi: set fdm=marker: