corrade-nucleus-nucleons – Blame information for rev 20

Subversion Repositories:
Rev:
Rev Author Line No. Line
20 office 1 var os = require('os');
2 var path = require('path');
3 var fs = require('fs');
4 var util = require('util');
5 var Promise = require('bluebird');
6 var whenReadFile = Promise.promisify(fs.readFile);
7  
8 var minimatch = require('./lib/fnmatch');
9 var iniparser = require('./lib/ini');
10 var Version = require('./lib/version');
11 var pkg = require('./package.json');
12  
13 var knownProps = [
14 'end_of_line',
15 'indent_style',
16 'indent_size',
17 'insert_final_newline',
18 'trim_trailing_whitespace',
19 'charset'
20 ].reduce(function (set, prop) {
21 set[prop] = true;
22 return set;
23 }, {});
24  
25 function fnmatch(filepath, glob) {
26 var matchOptions = {matchBase: true, dot: true, noext: true};
27 glob = glob.replace(/\*\*/g, '{*,**/**/**}');
28 return minimatch(filepath, glob, matchOptions);
29 }
30  
31 function getConfigFileNames(filepath, options) {
32 var paths = [];
33 do {
34 filepath = path.dirname(filepath);
35 paths.push(path.join(filepath, options.config));
36 } while (filepath !== options.root);
37 return paths;
38 }
39  
40 function getFilepathRoot(filepath) {
41 if (path.parse !== undefined) {
42 // Node.js >= 0.11.15
43 return path.parse(filepath).root;
44 }
45 if (os.platform() === 'win32') {
46 return path.resolve(filepath).match(/^(\\\\[^\\]+\\)?[^\\]+\\/)[0];
47 }
48 return '/';
49 }
50  
51 function processMatches(matches, version) {
52 // Set indent_size to "tab" if indent_size is unspecified and
53 // indent_style is set to "tab".
54 if ("indent_style" in matches && matches.indent_style === "tab" &&
55 !("indent_size" in matches) && version.gte(new Version(0, 10))) {
56 matches.indent_size = "tab";
57 }
58  
59 // Set tab_width to indent_size if indent_size is specified and
60 // tab_width is unspecified
61 if ("indent_size" in matches && !("tab_width" in matches) &&
62 matches.indent_size !== "tab")
63 matches.tab_width = matches.indent_size;
64  
65 // Set indent_size to tab_width if indent_size is "tab"
66 if("indent_size" in matches && "tab_width" in matches &&
67 matches.indent_size === "tab")
68 matches.indent_size = matches.tab_width;
69  
70 return matches;
71 }
72  
73 function processOptions(options, filepath) {
74 options = options || {};
75 return {
76 config: options.config || '.editorconfig',
77 version: new Version(options.version || pkg.version),
78 root: path.resolve(options.root || getFilepathRoot(filepath))
79 };
80 }
81  
82 function buildFullGlob(pathPrefix, glob) {
83 switch (glob.indexOf('/')) {
84 case -1: glob = "**/" + glob; break;
85 case 0: glob = glob.substring(1); break;
86 }
87 return path.join(pathPrefix, glob);
88 }
89  
90 function extendProps(props, options) {
91 for (var key in options) {
92 var value = options[key];
93 key = key.toLowerCase();
94 if (knownProps[key]) {
95 value = value.toLowerCase();
96 }
97 try {
98 value = JSON.parse(value);
99 } catch(e) {}
100 if (typeof value === 'undefined' || value === null) {
101 // null and undefined are values specific to JSON (no special meaning
102 // in editorconfig) & should just be returned as regular strings.
103 value = String(value);
104 }
105 props[key] = value;
106 }
107 return props;
108 }
109  
110 function parseFromFiles(filepath, files, options) {
111 return getConfigsForFiles(files).then(function (configs) {
112 return configs.reverse();
113 }).reduce(function (matches, file) {
114 var pathPrefix = path.dirname(file.name);
115 file.contents.forEach(function (section) {
116 var glob = section[0], options = section[1];
117 if (!glob) return;
118 var fullGlob = buildFullGlob(pathPrefix, glob);
119 if (!fnmatch(filepath, fullGlob)) return;
120 matches = extendProps(matches, options);
121 });
122 return matches;
123 }, {}).then(function (matches) {
124 return processMatches(matches, options.version);
125 });
126 }
127  
128 function parseFromFilesSync(filepath, files, options) {
129 var configs = getConfigsForFilesSync(files);
130 configs.reverse();
131 var matches = {};
132 configs.forEach(function(config) {
133 var pathPrefix = path.dirname(config.name);
134 config.contents.forEach(function(section) {
135 var glob = section[0], options = section[1];
136 if (!glob) return;
137 var fullGlob = buildFullGlob(pathPrefix, glob);
138 if (!fnmatch(filepath, fullGlob)) return;
139 matches = extendProps(matches, options);
140 });
141 });
142 return processMatches(matches, options.version);
143 }
144  
145 function StopReduce(array) {
146 this.array = array;
147 }
148  
149 StopReduce.prototype = Object.create(Error.prototype);
150  
151 function getConfigsForFiles(files) {
152 return Promise.reduce(files, function (configs, file) {
153 var contents = iniparser.parseString(file.contents);
154 configs.push({
155 name: file.name,
156 contents: contents
157 });
158 if ((contents[0][1].root || '').toLowerCase() === 'true') {
159 return Promise.reject(new StopReduce(configs));
160 }
161 return configs;
162 }, []).catch(StopReduce, function (stop) {
163 return stop.array;
164 });
165 }
166  
167 function getConfigsForFilesSync(files) {
168 var configs = [];
169 for (var i in files) {
170 var file = files[i];
171 var contents = iniparser.parseString(file.contents);
172 configs.push({
173 name: file.name,
174 contents: contents
175 });
176 if ((contents[0][1].root || '').toLowerCase() === 'true') {
177 break;
178 }
179 };
180 return configs;
181 }
182  
183 function readConfigFiles(filepaths) {
184 return Promise.map(filepaths, function (path) {
185 return whenReadFile(path, 'utf-8').catch(function () {
186 return '';
187 }).then(function (contents) {
188 return {name: path, contents: contents};
189 });
190 });
191 }
192  
193 function readConfigFilesSync(filepaths) {
194 var files = [];
195 var file;
196 filepaths.forEach(function(filepath) {
197 try {
198 file = fs.readFileSync(filepath, 'utf8');
199 } catch (e) {
200 file = '';
201 }
202 files.push({name: filepath, contents: file});
203 });
204 return files;
205 }
206  
207 module.exports.parseFromFiles = function (filepath, files, options) {
208 return new Promise (function (resolve, reject) {
209 filepath = path.resolve(filepath);
210 options = processOptions(options, filepath);
211 resolve(parseFromFiles(filepath, files, options));
212 });
213 };
214  
215 module.exports.parseFromFilesSync = function (filepath, files, options) {
216 filepath = path.resolve(filepath);
217 options = processOptions(options, filepath);
218 return parseFromFilesSync(filepath, files, options);
219 };
220  
221 module.exports.parse = function (filepath, options) {
222 return new Promise (function (resolve, reject) {
223 filepath = path.resolve(filepath);
224 options = processOptions(options, filepath);
225 var filepaths = getConfigFileNames(filepath, options);
226 var files = readConfigFiles(filepaths);
227 resolve(parseFromFiles(filepath, files, options));
228 });
229 };
230  
231 module.exports.parseSync = function (filepath, options) {
232 filepath = path.resolve(filepath);
233 options = processOptions(options, filepath);
234 var filepaths = getConfigFileNames(filepath, options);
235 var files = readConfigFilesSync(filepaths);
236 return parseFromFilesSync(filepath, files, options);
237 };