"""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"]