3 var stripJsonComments = require('strip-json-comments');
4 var fs = require('fs');
5 var path = require('path');
7 var _ws = require('rocambole-whitespace');
8 var _br = require('rocambole-linebreak');
9 var indent = require('./indent');
10 var plugins = require('./plugins');
12 var deepMixIn = require('mout/object/deepMixIn');
13 var merge = require('mout/object/merge');
14 var get = require('mout/object/get');
15 var isObject = require('mout/lang/isObject');
25 'default': require('./preset/default.json'),
26 'jquery' : require('./preset/jquery.json')
30 exports.set = function(opts) {
31 var preset = opts && opts.preset ? opts.preset : 'default';
32 // we need to pass all the user settings and default settings to the plugins
33 // so they are able to toggle the behavior and make changes based on the
35 _curOpts = mergeOptions(preset, opts);
37 _ws.setOptions(_curOpts.whiteSpace);
38 _br.setOptions(_curOpts.lineBreak);
39 indent.setOptions(_curOpts.indent);
40 plugins.setOptions(_curOpts);
42 // user provided options should override default settings and also any
43 // changes made by plugins
45 _curOpts = deepMixIn(_curOpts, opts);
50 function mergeOptions(preset, opts){
51 if (!(preset in exports.presets)) {
52 throw new Error('Invalid preset file "' + preset + '".');
54 var baseOpts = exports.presets[preset];
55 // recursively merge options to allow a "prototype chain"
56 if (baseOpts.preset) {
57 baseOpts = mergeOptions(baseOpts.preset, baseOpts);
59 return merge({}, baseOpts, opts);
63 exports.get = function(prop) {
64 return prop ? get(_curOpts, prop) : _curOpts;
68 exports.getRc = getRc;
69 function getRc(filePath, customOptions) {
70 // if user sets the "preset" we don't load any other config file
71 // we assume the "preset" overrides any user settings
72 if (isTopLevel(customOptions)) {
76 if (isObject(filePath)) {
77 customOptions = filePath;
80 // we search for config file starting from source directory or from cwd if
81 // path is not provided
82 var basedir = filePath ? path.dirname(filePath) : process.cwd();
83 var cwd = process.cwd();
84 var rc = findAndMergeConfigs(basedir);
85 if (!rc && basedir !== cwd) {
86 rc = findAndMergeConfigs(cwd);
88 return merge(rc || getGlobalConfig(), customOptions);
92 function findAndMergeConfigs(basedir) {
93 if (!basedir || !basedir.length) return;
95 var configFiles = ['.esformatter', 'package.json'];
98 configFiles.some(function(name) {
99 var filePath = path.join(basedir, name);
100 if (!fs.existsSync(filePath)) return;
102 var cur = loadAndParseConfig(filePath);
103 if (name === 'package.json') {
104 cur = cur.esformatter;
109 // we merge configs on same folder as well just in case user have
110 // ".esformatter" and "package.json" on same folder
111 // notice that ".esformatter" file takes precedence and will override the
112 // "package.json" settings.
113 config = config ? merge(cur, config) : cur;
116 if (isTopLevel(config)) return true;
119 if (isTopLevel(config)) {
123 // we merge configs from parent folders so it's easier to add different rules
124 // for each folder on a project and/or override just specific settings
125 var parentDir = path.resolve(basedir, '..');
126 // we need to check if parentDir is different from basedir to avoid conflicts
127 // on windows (see #174)
128 var parentConfig = parentDir && parentDir !== basedir ?
129 findAndMergeConfigs(parentDir) :
131 // notice that current folder config overrides the parent folder config
132 return merge(parentConfig, config);
136 function isTopLevel(config) {
137 // if config contains 'root:true' or inherit from another "preset" we
138 // consider it as top-level and don't merge the settings with config files on
140 return config && (config.root || config.preset);
144 function getGlobalConfig() {
145 var home = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
146 var file = path.join(home, '.esformatter');
147 return fs.existsSync(file) ? loadAndParseConfig(file) : {};
151 exports.loadAndParseConfig = loadAndParseConfig;
152 function loadAndParseConfig(file) {
154 return JSON.parse(stripJsonComments(fs.readFileSync(file).toString()));
156 console.error('Can\'t parse configuration file: "' + file + '"\nException: ' + ex.message);