3 var promise = require('bluebird'),
4 isHTML = require('is-html'),
5 isURL = require('is-absolute-url'),
7 path = require('path'),
10 var fs = promise.promisifyAll(require('fs'), { multiArgs: true }),
11 request = promise.promisify(require('request'), { multiArgs: true });
13 function isWindows() {
14 return os.platform() === 'win32';
18 * Check if the supplied string might be a RegExp and, if so, return the corresponding RegExp.
19 * @param {String} str The regex to transform.
20 * @return {RegExp|String} The final RegExp
22 function strToRegExp(str) {
24 return new RegExp(str.replace(/^\/|\/$/g, ''));
30 * Parse a given uncssrc file.
31 * @param {String} filename The location of the uncssrc file
32 * @return {Object} The options object
34 function parseUncssrc(filename) {
35 var options = JSON.parse(fs.readFileSync(filename, 'utf-8'));
37 /* RegExps can't be stored as JSON, therefore we need to parse them manually.
38 * A string is a RegExp if it starts with '/', since that wouldn't be a valid CSS selector.
40 options.ignore = options.ignore ? options.ignore.map(strToRegExp) : undefined;
41 options.ignoreSheets = options.ignoreSheets ? options.ignoreSheets.map(strToRegExp) : undefined;
47 * Parse paths relatives to a source.
48 * @param {String} source Where the paths originate from
49 * @param {Array} stylesheets List of paths
50 * @param {Object} options Options, as passed to UnCSS
51 * @return {Array} List of paths
53 function parsePaths(source, stylesheets, options) {
54 return stylesheets.map(function (sheet) {
57 if (sheet.substr(0, 4) === 'http') {
58 /* No need to parse, it's already a valid path */
62 /* Check if we are fetching over http(s) */
64 sourceProtocol = url.parse(source).protocol;
66 if (sheet.substr(0, 2) === '//') {
67 /* Use the same protocol we used for fetching this page.
70 return sourceProtocol ? sourceProtocol + sheet : 'http:' + sheet;
72 return url.resolve(source, sheet);
75 /* We are fetching local files
76 * Should probably report an error if we find an absolute path and
77 * have no htmlroot specified.
79 /* Fix the case when there is a query string or hash */
80 sheet = sheet.split('?')[0].split('#')[0];
82 /* Path already parsed by PhantomJS */
83 if (sheet.substr(0, 5) === 'file:') {
84 sheet = url.parse(sheet).path.replace('%20', ' ');
85 /* If on windows, remove first '/' */
86 sheet = isWindows() ? sheet.substring(1) : sheet;
88 if (options.htmlroot) {
89 return path.join(options.htmlroot, sheet);
91 sheet = path.relative(path.join(path.dirname(source)), sheet);
94 if (sheet[0] === '/' && options.htmlroot) {
95 return path.join(options.htmlroot, sheet);
96 } else if (isHTML(source)) {
97 return path.join(options.csspath, sheet);
99 return path.join(path.dirname(source), options.csspath, sheet);
104 * Given an array of filenames, return an array of the files' contents,
105 * only if the filename matches a regex
106 * @param {Array} files An array of the filenames to read
109 function readStylesheets(files) {
110 return promise.map(files, function (filename) {
111 if (isURL(filename)) {
114 headers: { 'User-Agent': 'UnCSS' }
115 }).spread(function (response, body) {
118 } else if (fs.existsSync(filename)) {
119 return fs.readFileAsync(filename, 'utf-8').then(function (contents) {
123 throw new Error('UnCSS: could not open ' + path.join(process.cwd(), filename));
124 }).then(function (res) {
125 // res is an array of the content of each file in files (in the same order)
126 for (var i = 0, len = files.length; i < len; i++) {
127 // We append a small banner to keep track of which file we are currently processing
128 // super helpful for debugging
129 var banner = '/*** uncss> filename: ' + files[i].replace(/\\/g, '/') + ' ***/\n';
130 res[i] = banner + res[i];
136 function parseErrorMessage(error, cssStr) {
138 /* Base line for conveying the line number in the error message */
142 var lines = cssStr.split('\n');
144 /* We get the filename of the css file that contains the error */
145 var i = error.line - 1;
146 while (i >= 0 && !error.filename) {
147 if (lines[i].substr(0, 21) === '/*** uncss> filename:') {
148 error.filename = lines[i].substring(22, lines[i].length - 4);
153 for (var j = error.line - 6; j < error.line + 5; j++) {
154 if (j - zeroLine < 0 || j >= lines.length) {
158 /* It could be minified CSS */
159 if (line.length > 120 && error.column) {
160 line = line.substring(error.column - 40, error.column);
162 error.message += '\n\t' + (j + 1 - zeroLine) + ': ';
163 error.message += j === error.line - 1 ? ' -> ' : ' ';
164 error.message += line;
169 error.message = error.message.replace(/[0-9]+:/, error.line - zeroLine + ':');
171 error.message = 'uncss/node_modules/css: unable to parse ' + error.filename + ':\n' + error.message + '\n';
176 isWindows: isWindows,
177 parseUncssrc: parseUncssrc,
178 parseErrorMessage: parseErrorMessage,
179 parsePaths: parsePaths,
180 readStylesheets: readStylesheets