|
1 """EditorConfig file handler |
|
2 |
|
3 Provides ``EditorConfigHandler`` class for locating and parsing |
|
4 EditorConfig files relevant to a given filepath. |
|
5 |
|
6 Licensed under PSF License (see LICENSE.txt file). |
|
7 |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from editorconfig import VERSION |
|
13 from editorconfig.ini import EditorConfigParser |
|
14 from editorconfig.exceptions import PathError, VersionError |
|
15 |
|
16 |
|
17 __all__ = ['EditorConfigHandler'] |
|
18 |
|
19 |
|
20 def get_filenames(path, filename): |
|
21 """Yield full filepath for filename in each directory in and above path""" |
|
22 path_list = [] |
|
23 while True: |
|
24 path_list.append(os.path.join(path, filename)) |
|
25 newpath = os.path.dirname(path) |
|
26 if path == newpath: |
|
27 break |
|
28 path = newpath |
|
29 return path_list |
|
30 |
|
31 |
|
32 class EditorConfigHandler(object): |
|
33 |
|
34 """ |
|
35 Allows locating and parsing of EditorConfig files for given filename |
|
36 |
|
37 In addition to the constructor a single public method is provided, |
|
38 ``get_configurations`` which returns the EditorConfig options for |
|
39 the ``filepath`` specified to the constructor. |
|
40 |
|
41 """ |
|
42 |
|
43 def __init__(self, filepath, conf_filename='.editorconfig', |
|
44 version=VERSION): |
|
45 """Create EditorConfigHandler for matching given filepath""" |
|
46 self.filepath = filepath |
|
47 self.conf_filename = conf_filename |
|
48 self.version = version |
|
49 self.options = None |
|
50 |
|
51 def get_configurations(self): |
|
52 |
|
53 """ |
|
54 Find EditorConfig files and return all options matching filepath |
|
55 |
|
56 Special exceptions that may be raised by this function include: |
|
57 |
|
58 - ``VersionError``: self.version is invalid EditorConfig version |
|
59 - ``PathError``: self.filepath is not a valid absolute filepath |
|
60 - ``ParsingError``: improperly formatted EditorConfig file found |
|
61 |
|
62 """ |
|
63 |
|
64 self.check_assertions() |
|
65 path, filename = os.path.split(self.filepath) |
|
66 conf_files = get_filenames(path, self.conf_filename) |
|
67 |
|
68 # Attempt to find and parse every EditorConfig file in filetree |
|
69 for filename in conf_files: |
|
70 parser = EditorConfigParser(self.filepath) |
|
71 parser.read(filename) |
|
72 |
|
73 # Merge new EditorConfig file's options into current options |
|
74 old_options = self.options |
|
75 self.options = parser.options |
|
76 if old_options: |
|
77 self.options.update(old_options) |
|
78 |
|
79 # Stop parsing if parsed file has a ``root = true`` option |
|
80 if parser.root_file: |
|
81 break |
|
82 |
|
83 self.preprocess_values() |
|
84 return self.options |
|
85 |
|
86 def check_assertions(self): |
|
87 |
|
88 """Raise error if filepath or version have invalid values""" |
|
89 |
|
90 # Raise ``PathError`` if filepath isn't an absolute path |
|
91 if not os.path.isabs(self.filepath): |
|
92 raise PathError("Input file must be a full path name.") |
|
93 |
|
94 # Raise ``VersionError`` if version specified is greater than current |
|
95 if self.version is not None and self.version[:3] > VERSION[:3]: |
|
96 raise VersionError( |
|
97 "Required version is greater than the current version.") |
|
98 |
|
99 def preprocess_values(self): |
|
100 |
|
101 """Preprocess option values for consumption by plugins""" |
|
102 |
|
103 opts = self.options |
|
104 |
|
105 # Lowercase option value for certain options |
|
106 for name in ["end_of_line", "indent_style", "indent_size", |
|
107 "insert_final_newline", "trim_trailing_whitespace", |
|
108 "charset"]: |
|
109 if name in opts: |
|
110 opts[name] = opts[name].lower() |
|
111 |
|
112 # Set indent_size to "tab" if indent_size is unspecified and |
|
113 # indent_style is set to "tab". |
|
114 if (opts.get("indent_style") == "tab" and |
|
115 not "indent_size" in opts and self.version >= (0, 10, 0)): |
|
116 opts["indent_size"] = "tab" |
|
117 |
|
118 # Set tab_width to indent_size if indent_size is specified and |
|
119 # tab_width is unspecified |
|
120 if ("indent_size" in opts and "tab_width" not in opts and |
|
121 opts["indent_size"] != "tab"): |
|
122 opts["tab_width"] = opts["indent_size"] |
|
123 |
|
124 # Set indent_size to tab_width if indent_size is "tab" |
|
125 if ("indent_size" in opts and "tab_width" in opts and |
|
126 opts["indent_size"] == "tab"): |
|
127 opts["indent_size"] = opts["tab_width"] |