Permalink
Browse files

bump editorconfig-core-py to 0.12.0

fixes #39
  • Loading branch information...
1 parent 26eed39 commit 6b95969c65d6132545203868904c58fd4cf7518f @sindresorhus committed Nov 23, 2014
Showing with 210 additions and 87 deletions.
  1. +1 −1 editorconfig/__init__.py
  2. +10 −5 editorconfig/compat.py
  3. +152 −59 editorconfig/fnmatch.py
  4. +7 −6 editorconfig/handler.py
  5. +32 −8 editorconfig/ini.py
  6. +8 −6 editorconfig/main.py
  7. +0 −2 editorconfig/odict.py
@@ -2,7 +2,7 @@
from editorconfig.versiontools import join_version
-VERSION = (0, 11, 3, "final")
+VERSION = (0, 12, 0, "final")
__all__ = ['get_properties', 'EditorConfigError', 'exceptions']
@@ -1,14 +1,19 @@
-"""EditorConfig Python2/Python3/Jython compatibility utilities"""
+"""EditorConfig Python2/Python3 compatibility utilities"""
import sys
-import types
-__all__ = ['slice', 'u']
+__all__ = ['force_unicode', 'u']
if sys.version_info[0] == 2:
- slice = types.SliceType
+ text_type = unicode
else:
- slice = slice
+ text_type = str
+
+
+def force_unicode(string):
+ if not isinstance(string, text_type):
+ string = text_type(string, encoding='utf-8')
+ return string
if sys.version_info[0] == 2:
@@ -24,6 +24,42 @@
_cache = {}
+LEFT_BRACE = re.compile(
+ r"""
+
+ (?: ^ | [^\\] ) # Beginning of string or a character besides "\"
+
+ \{ # "{"
+
+ """, re.VERBOSE
+)
+
+RIGHT_BRACE = re.compile(
+ r"""
+
+ (?: ^ | [^\\] ) # Beginning of string or a character besides "\"
+
+ \} # "}"
+
+ """, re.VERBOSE
+)
+
+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
+)
+
def fnmatch(name, pat):
"""Test whether FILENAME matches PATTERN.
@@ -47,80 +83,137 @@ def fnmatch(name, pat):
return fnmatchcase(name, pat)
+def cached_translate(pat):
+ if not pat in _cache:
+ res, num_groups = translate(pat)
+ regex = re.compile(res)
+ _cache[pat] = regex, num_groups
+ return _cache[pat]
+
+
def fnmatchcase(name, pat):
"""Test whether FILENAME matches PATTERN, including case.
This is a version of fnmatch() which doesn't case-normalize
its arguments.
"""
- if not pat in _cache:
- res = translate(pat)
- _cache[pat] = re.compile(res)
- return _cache[pat].match(name) is not None
+ regex, num_groups = cached_translate(pat)
+ match = regex.match(name)
+ if not match:
+ return False
+ pattern_matched = True
+ for (num, (min_num, max_num)) in zip(match.groups(), num_groups):
+ if num[0] == '0' or not (min_num <= int(num) <= max_num):
+ pattern_matched = False
+ break
+ return pattern_matched
-def translate(pat):
+def translate(pat, nested=False):
"""Translate a shell PATTERN to a regular expression.
There is no way to quote meta-characters.
"""
- i, n = 0, len(pat)
- res = ''
- escaped = False
- while i < n:
- c = pat[i]
- i = i + 1
- if c == '*':
- j = i
- if j < n and pat[j] == '*':
- res = res + '.*'
+ index, length = 0, len(pat) # Current index and length of pattern
+ brace_level = 0
+ in_brackets = False
+ result = ''
+ is_escaped = False
+ matching_braces = (len(LEFT_BRACE.findall(pat)) ==
+ len(RIGHT_BRACE.findall(pat)))
+ numeric_groups = []
+ while index < length:
+ current_char = pat[index]
+ index += 1
+ if current_char == '*':
+ pos = index
+ if pos < length and pat[pos] == '*':
+ result += '.*'
+ else:
+ result += '[^/]*'
+ elif current_char == '?':
+ result += '.'
+ elif current_char == '[':
+ if in_brackets:
+ result += '\\['
+ else:
+ pos = index
+ has_slash = False
+ while pos < length and pat[pos] != ']':
+ if pat[pos] == '/' and pat[pos-1] != '\\':
+ has_slash = True
+ break
+ pos += 1
+ if has_slash:
+ result += '\\[' + pat[index:(pos + 1)] + '\\]'
+ index = pos + 2
+ else:
+ if index < length and pat[index] in '!^':
+ index += 1
+ result += '[^'
+ else:
+ result += '['
+ in_brackets = True
+ elif current_char == '-':
+ if in_brackets:
+ result += current_char
+ else:
+ result += '\\' + current_char
+ elif current_char == ']':
+ result += current_char
+ in_brackets = False
+ elif current_char == '{':
+ pos = index
+ has_comma = False
+ while pos < length and (pat[pos] != '}' or is_escaped):
+ if pat[pos] == ',' and not is_escaped:
+ has_comma = True
+ break
+ is_escaped = pat[pos] == '\\' and not is_escaped
+ pos += 1
+ if not has_comma and pos < length:
+ num_range = NUMERIC_RANGE.match(pat[index:pos])
+ if num_range:
+ numeric_groups.append(map(int, num_range.groups()))
+ result += "([+-]?\d+)"
+ else:
+ inner_result, inner_groups = translate(pat[index:pos],
+ nested=True)
+ result += '\\{%s\\}' % (inner_result,)
+ numeric_groups += inner_groups
+ index = pos + 1
+ elif matching_braces:
+ result += '(?:'
+ brace_level += 1
+ else:
+ result += '\\{'
+ elif current_char == ',':
+ if brace_level > 0 and not is_escaped:
+ result += '|'
else:
- res = res + '[^/]*'
- elif c == '?':
- res = res + '.'
- elif c == '[':
- j = i
- if j < n and pat[j] == '!':
- j = j + 1
- if j < n and pat[j] == ']':
- j = j + 1
- while j < n and (pat[j] != ']' or escaped):
- escaped = pat[j] == '\\' and not escaped
- j = j + 1
- if j >= n:
- res = res + '\\['
+ result += '\\,'
+ elif current_char == '}':
+ if brace_level > 0 and not is_escaped:
+ result += ')'
+ brace_level -= 1
else:
- stuff = pat[i:j]
- i = j + 1
- if stuff[0] == '!':
- stuff = '^' + stuff[1:]
- elif stuff[0] == '^':
- stuff = '\\' + stuff
- res = '%s[%s]' % (res, stuff)
- elif c == '{':
- j = i
- groups = []
- while j < n and pat[j] != '}':
- k = j
- while k < n and (pat[k] not in (',', '}') or escaped):
- escaped = pat[k] == '\\' and not escaped
- k = k + 1
- group = pat[j:k]
- for char in (',', '}', '\\'):
- group = group.replace('\\' + char, char)
- groups.append(group)
- j = k
- if j < n and pat[j] == ',':
- j = j + 1
- if j < n and pat[j] == '}':
- groups.append('')
- if j >= n or len(groups) < 2:
- res = res + '\\{'
+ result += '\\}'
+ elif current_char == '/':
+ if pat[index:(index + 3)] == "**/":
+ result += "(?:/|/.*/)"
+ index += 3
else:
- res = '%s(%s)' % (res, '|'.join(map(re.escape, groups)))
- i = j + 1
+ result += '/'
+ elif current_char != '\\':
+ result += re.escape(current_char)
+ if current_char == '\\':
+ if is_escaped:
+ result += re.escape(current_char)
+ is_escaped = not is_escaped
else:
- res = res + re.escape(c)
- return res + '\Z(?ms)'
+ is_escaped = False
+ if not nested:
+ result += '\Z(?ms)'
+ return result, numeric_groups
@@ -41,7 +41,7 @@ class EditorConfigHandler(object):
"""
def __init__(self, filepath, conf_filename='.editorconfig',
- version=VERSION):
+ version=VERSION):
"""Create EditorConfigHandler for matching given filepath"""
self.filepath = filepath
self.conf_filename = conf_filename
@@ -94,7 +94,7 @@ def check_assertions(self):
# Raise ``VersionError`` if version specified is greater than current
if self.version is not None and self.version[:3] > VERSION[:3]:
raise VersionError(
- "Required version is greater than the current version.")
+ "Required version is greater than the current version.")
def preprocess_values(self):
@@ -104,23 +104,24 @@ def preprocess_values(self):
# Lowercase option value for certain options
for name in ["end_of_line", "indent_style", "indent_size",
- "insert_final_newline", "trim_trailing_whitespace", "charset"]:
+ "insert_final_newline", "trim_trailing_whitespace",
+ "charset"]:
if name in opts:
opts[name] = opts[name].lower()
# Set indent_size to "tab" if indent_size is unspecified and
# indent_style is set to "tab".
if (opts.get("indent_style") == "tab" and
- not "indent_size" in opts and self.version >= (0, 10, 0)):
+ not "indent_size" in opts and self.version >= (0, 10, 0)):
opts["indent_size"] = "tab"
# Set tab_width to indent_size if indent_size is specified and
# tab_width is unspecified
if ("indent_size" in opts and "tab_width" not in opts and
- opts["indent_size"] != "tab"):
+ opts["indent_size"] != "tab"):
opts["tab_width"] = opts["indent_size"]
# Set indent_size to tab_width if indent_size is "tab"
if ("indent_size" in opts and "tab_width" in opts and
- opts["indent_size"] == "tab"):
+ opts["indent_size"] == "tab"):
opts["indent_size"] = opts["tab_width"]
View
@@ -17,7 +17,7 @@
from codecs import open
import posixpath
from os import sep
-from os.path import normcase, dirname
+from os.path import normpath, dirname
from editorconfig.exceptions import ParsingError
from editorconfig.fnmatch import fnmatch
@@ -38,19 +38,43 @@ class EditorConfigParser(object):
# Regular expressions for parsing section headers and options.
# Allow ``]`` and escaped ``;`` and ``#`` characters in section headers
SECTCRE = re.compile(
- r'\s*\[' # [
- r'(?P<header>([^#;]|\\#|\\;)+)' # very permissive!
- r'\]' # ]
+ r"""
+
+ \s * # Optional whitespace
+ \[ # Opening square brace
+
+ (?P<header> # One or more characters excluding
+ ( [^\#;] | \\\# | \\; ) + # unescaped # and ; characters
)
+
+ \] # Closing square brace
+
+ """, re.VERBOSE
+ )
# 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
OPTCRE = re.compile(
- r'\s*(?P<option>[^:=\s][^:=]*)'
- r'\s*(?P<vi>[:=])\s*'
- r'(?P<value>.*)$'
+ r"""
+
+ \s * # Optional whitespace
+ (?P<option> # One or more characters excluding
+ [^:=\s] # : a = characters (and first
+ [^:=] * # must not be whitespace)
+ )
+ \s * # Optional whitespace
+ (?P<vi>
+ [:=] # Single = or : character
)
+ \s * # Optional whitespace
+ (?P<value>
+ . * # One or more characters
+ )
+ $
+
+ """, re.VERBOSE
+ )
def __init__(self, filename):
self.filename = filename
@@ -59,7 +83,7 @@ def __init__(self, filename):
def matches_filename(self, config_filename, glob):
"""Return True if section glob matches filename"""
- config_dirname = normcase(dirname(config_filename)).replace(sep, '/')
+ config_dirname = normpath(dirname(config_filename)).replace(sep, '/')
glob = glob.replace("\\#", "#")
glob = glob.replace("\\;", ";")
if '/' in glob:
Oops, something went wrong.

0 comments on commit 6b95969

Please sign in to comment.