Security update for permissions_by_term
[yaffs-website] / node_modules / uncss / src / utility.js
1 'use strict';
2
3 var promise = require('bluebird'),
4     isHTML = require('is-html'),
5     isURL = require('is-absolute-url'),
6     os = require('os'),
7     path = require('path'),
8     url = require('url');
9
10 var fs = promise.promisifyAll(require('fs'), { multiArgs: true }),
11     request = promise.promisify(require('request'), { multiArgs: true });
12
13 function isWindows() {
14     return os.platform() === 'win32';
15 }
16
17 /**
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
21  */
22 function strToRegExp(str) {
23     if (str[0] === '/') {
24         return new RegExp(str.replace(/^\/|\/$/g, ''));
25     }
26     return str;
27 }
28
29 /**
30  * Parse a given uncssrc file.
31  * @param  {String} filename The location of the uncssrc file
32  * @return {Object}          The options object
33  */
34 function parseUncssrc(filename) {
35     var options = JSON.parse(fs.readFileSync(filename, 'utf-8'));
36
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.
39      */
40     options.ignore = options.ignore ? options.ignore.map(strToRegExp) : undefined;
41     options.ignoreSheets = options.ignoreSheets ? options.ignoreSheets.map(strToRegExp) : undefined;
42
43     return options;
44 }
45
46 /**
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
52  */
53 function parsePaths(source, stylesheets, options) {
54     return stylesheets.map(function (sheet) {
55         var sourceProtocol;
56
57         if (sheet.substr(0, 4) === 'http') {
58             /* No need to parse, it's already a valid path */
59             return sheet;
60         }
61
62         /* Check if we are fetching over http(s) */
63         if (isURL(source)) {
64             sourceProtocol = url.parse(source).protocol;
65
66             if (sheet.substr(0, 2) === '//') {
67                 /* Use the same protocol we used for fetching this page.
68                  * Default to http.
69                  */
70                 return sourceProtocol ? sourceProtocol + sheet : 'http:' + sheet;
71             }
72             return url.resolve(source, sheet);
73         }
74
75         /* We are fetching local files
76          * Should probably report an error if we find an absolute path and
77          *   have no htmlroot specified.
78          */
79         /* Fix the case when there is a query string or hash */
80         sheet = sheet.split('?')[0].split('#')[0];
81
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;
87
88             if (options.htmlroot) {
89                 return path.join(options.htmlroot, sheet);
90             }
91             sheet = path.relative(path.join(path.dirname(source)), sheet);
92         }
93
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);
98         }
99         return path.join(path.dirname(source), options.csspath, sheet);
100     });
101 }
102
103 /**
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
107  * @return {promise}
108  */
109 function readStylesheets(files) {
110     return promise.map(files, function (filename) {
111         if (isURL(filename)) {
112             return request({
113                 url: filename,
114                 headers: { 'User-Agent': 'UnCSS' }
115             }).spread(function (response, body) {
116                 return body;
117             });
118         } else if (fs.existsSync(filename)) {
119             return fs.readFileAsync(filename, 'utf-8').then(function (contents) {
120                 return contents;
121             });
122         }
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];
131         }
132         return res;
133     });
134 }
135
136 function parseErrorMessage(error, cssStr) {
137     /* TODO: FIXME */
138     /* Base line for conveying the line number in the error message */
139     var zeroLine = 0;
140
141     if (error.line) {
142         var lines = cssStr.split('\n');
143         if (lines.length) {
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);
149                     zeroLine = i;
150                 }
151                 i--;
152             }
153             for (var j = error.line - 6; j < error.line + 5; j++) {
154                 if (j - zeroLine < 0 || j >= lines.length) {
155                     continue;
156                 }
157                 var line = lines[j];
158                 /* It could be minified CSS */
159                 if (line.length > 120 && error.column) {
160                     line = line.substring(error.column - 40, error.column);
161                 }
162                 error.message += '\n\t' + (j + 1 - zeroLine) + ':    ';
163                 error.message += j === error.line - 1 ? ' -> ' : '    ';
164                 error.message += line;
165             }
166         }
167     }
168     if (zeroLine > 0) {
169         error.message = error.message.replace(/[0-9]+:/, error.line - zeroLine + ':');
170     }
171     error.message = 'uncss/node_modules/css: unable to parse ' + error.filename + ':\n' + error.message + '\n';
172     return error;
173 }
174
175 module.exports = {
176     isWindows: isWindows,
177     parseUncssrc: parseUncssrc,
178     parseErrorMessage: parseErrorMessage,
179     parsePaths: parsePaths,
180     readStylesheets: readStylesheets
181 };