You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
4.2 KiB
Python

"""EditorConfig file handler
Provides ``EditorConfigHandler`` class for locating and parsing
EditorConfig files relevant to a given filepath.
Licensed under PSF License (see LICENSE.txt file).
"""
import os
from editorconfig import VERSION
from editorconfig.ini import EditorConfigParser
from editorconfig.exceptions import PathError, VersionError
__all__ = ['EditorConfigHandler']
def get_filenames(path, filename):
"""Yield full filepath for filename in each directory in and above path"""
path_list = []
while True:
path_list.append(os.path.join(path, filename))
newpath = os.path.dirname(path)
if path == newpath:
break
path = newpath
return path_list
class EditorConfigHandler(object):
"""
Allows locating and parsing of EditorConfig files for given filename
In addition to the constructor a single public method is provided,
``get_configurations`` which returns the EditorConfig options for
the ``filepath`` specified to the constructor.
"""
def __init__(self, filepath, conf_filename='.editorconfig',
version=VERSION):
"""Create EditorConfigHandler for matching given filepath"""
self.filepath = filepath
self.conf_filename = conf_filename
self.version = version
self.options = None
def get_configurations(self):
"""
Find EditorConfig files and return all options matching filepath
Special exceptions that may be raised by this function include:
- ``VersionError``: self.version is invalid EditorConfig version
- ``PathError``: self.filepath is not a valid absolute filepath
- ``ParsingError``: improperly formatted EditorConfig file found
"""
self.check_assertions()
path, filename = os.path.split(self.filepath)
conf_files = get_filenames(path, self.conf_filename)
# Attempt to find and parse every EditorConfig file in filetree
for filename in conf_files:
parser = EditorConfigParser(self.filepath)
parser.read(filename)
# Merge new EditorConfig file's options into current options
old_options = self.options
self.options = parser.options
if old_options:
self.options.update(old_options)
# Stop parsing if parsed file has a ``root = true`` option
if parser.root_file:
break
self.preprocess_values()
return self.options
def check_assertions(self):
"""Raise error if filepath or version have invalid values"""
# Raise ``PathError`` if filepath isn't an absolute path
if not os.path.isabs(self.filepath):
raise PathError("Input file must be a full path name.")
# 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.")
def preprocess_values(self):
"""Preprocess option values for consumption by plugins"""
opts = self.options
# Lowercase option value for certain options
for name in ["end_of_line", "indent_style", "indent_size",
"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)):
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["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"] = opts["tab_width"]