Permalink
Fetching contributors…
Cannot retrieve contributors at this time
468 lines (420 sloc) 15.3 KB
<% var MDN = module.exports = {
/**
* Return an HTML link using the given url, text, and title.
*/
link: function (url, text, title) {
var e = MDN.htmlEscapeArgs(['url', 'text', 'title'], arguments);
return [
'<a href="', e.url, '" title="', e.title, '">',
e.text,
'</a>'
].join('');
},
/**
* Given a set of names and a corresponding list of values, apply HTML
* escaping to each of the values and return an object with the results
* associated with the names.
*/
htmlEscapeArgs: function (names, args) {
var e = {};
names.forEach(function (name, idx) {
e[name] = kuma.htmlEscape(args[idx]);
});
return e;
},
/**
* Given a set of strings like this:
* { "en-US": "Foo", "de": "Bar", "es": "Baz" }
* Return the one which matches the current locale.
*/
localString: function (strings) {
var lang = env.locale;
if (!(lang in strings)) lang = 'en-US';
return strings[lang];
},
/**
* Given a set of string maps like this:
* { "en-US": {"name": "Foo"}, "de": {"name": "Bar"} }
* Return a map which matches the current locale, falling back to en-US
* properties when the localized map contains partial properties.
*/
localStringMap: function (maps) {
var lang = env.locale;
var defaultMap = maps['en-US'];
if (lang == 'en-US' || !(lang in maps)) {
return defaultMap;
}
var localizedMap = maps[lang];
var map = {};
for (var name in defaultMap) {
if (name in localizedMap) {
map[name] = localizedMap[name];
} else {
map[name] = defaultMap[name];
}
}
return map;
},
/**
* Given a set of strings like this:
* {
* "hello": { "en-US": "Hello!", "de": "Hallo!" },
* "bye": { "en-US": "Goodbye!", "de": "Auf Wiedersehen!" }
* }
* Returns the one, which matches the current locale.
*
* Example:
* getLocalString({"hello": {"en-US": "Hello!", "de": "Hallo!"}},
* "hello");
* => "Hallo!" (in case the locale is 'de')
*/
getLocalString: function (strings, key) {
if (!strings.hasOwnProperty(key)) {
return key;
}
var lang = env.locale;
if (!(lang in strings[key])) {
lang = 'en-US';
}
return strings[key][lang];
},
/**
* Given a string, replaces all placeholders outlined by
* $1$, $2$, etc. (i.e. numeric placeholders) or
* $firstVariable$, $secondVariable$, etc. (i.e. string placeholders)
* within it.
*
* If numeric placeholders are used, the 'replacements' parameter must be
* an array. The number within the placeholder indicates the index within the
* replacement array starting by 1.
* If string placeholders are used, the 'replacements' parameter must be an
* object. Its property names represent the placeholder names and their
* values the values to be inserted.
*
* Examples:
* replacePlaceholders("$1$ $2$, $1$ $3$!", ["hello", "world", "contributor"])
* => "hello world, hello contributor!"
*
* replacePlaceholders("$hello$ $world$, $hello$ $contributor$!",
* {hello: "hallo", world: "Welt", contributor: "Mitwirkender"})
* => "hallo Welt, hallo Mitwirkender!"
*/
replacePlaceholders: function (string, replacements) {
function replacePlaceholder(placeholder, offset, string) {
var index = placeholder.substring(1, placeholder.length - 1);
if (!Number.isNaN(Number(index))) {
index--;
}
return index in replacements ? replacements[index] : '';
}
return string.replace(/\$\w+\$/g, replacePlaceholder);
},
/**
* Given a string, escapes all quotes within it.
*/
escapeQuotes: function(a) {
var b = "";
for (var i = 0, len = a.length; i < len; i++) {
var c = a[i];
if (c=="\"") c = "&quot;";
b += c;
}
return b.replace(/(<([^>]+)>)/ig, "");
},
/**
* #### fetchCompatTableJSON
* Sets proper headers and makes a request to get compat table data
*/
fetchCompatTableJSON: function(url) {
var options = {
headers: {
'Accept': 'application/vnd.api+json',
'Content-Type': 'application/json'
}
};
return JSON.parse(this.fetchHTTPResource(url, options));
},
/**
* Accepts a relative URL or an attachment object
* Returns the content of a given file.
*/
getFileContent: function(fileObjOrUrl) {
var url = fileObjOrUrl.url || fileObjOrUrl;
if(!url) return '';
var result = '',
base_url = '';
// New file urls include attachment host, so we don't need to prepend it
var fUrl = kuma.url.parse(url);
if (!fUrl.host) {
var p = kuma.url.parse(env.url, true),
base_url = p.protocol + '//' + p.host;
}
url = base_url + url;
return cacheFn('kuma:get_attachment_content:' + md5(url.toLowerCase()), 3600, function(next) {
try {
request({
method: 'GET',
headers: { 'Cache-Control': env.cache_control },
url: url
}, function(err, resp, body) {
if(resp && 200 == resp.statusCode) {
next(body);
}
else if(err) {
next(null);
}
});
} catch(e) {
next("error: " + e);
}
});
},
/**
* #### cacheFnIgnoreCacheControl
* Cache a function, and use cached results no matter what
* Cache-Control we get from a shift-refresh.
*/
cacheFnIgnoreCacheControl: function (key, tm_out, to_cache) {
var result = null,
err_result = null,
f = new Future(),
mc = memcached;
mc.get(key, function (err, c_result) {
if (c_result) {
result = c_result;
f['return']();
} else {
try {
to_cache(function (val) {
mc.set(key, val, tm_out, function (err, c_result) {
result = val;
f['return']();
});
});
} catch (e) {
err_result = e;
result = '';
f['return']();
}
}
});
f.wait();
if (err_result) { throw err_result; }
return result;
},
// #### memcacheSet(key, value, timeout)
//
// Store a value in memcache with the given key and timeout (in seconds)
memcacheSet: function (key, value, tmout) {
var result = null,
err_result = null,
f = new Future(),
mc = memcached;
mc.set(key, value, tmout, function (err, c_result) {
err_result = err;
result = c_result;
f['return']();
});
f.wait();
if (err_result) { throw err_result; }
return result;
},
// #### memcacheGet(key)
//
// Fetch the value for a key from memcache
memcacheGet: function (key) {
var result = null,
err_result = null,
f = new Future(),
mc = memcached;
mc.get(key, function (err, c_result) {
err_result = err;
result = c_result;
f['return']();
});
f.wait();
if (err_result) { throw err_result; }
return result;
},
// #### defaults(object, *defaults)
//
// Fill in undefined properties in object with values from the defaults
// objects, and return the object. As soon as the property is filled,
// further defaults will have no effect.
//
// Stolen from http://underscorejs.org/#defaults
defaults: function (obj) {
Array.prototype.slice.call(arguments, 1).forEach(function(source) {
if (source) {
for (var prop in source) {
if (obj[prop] === void 0) obj[prop] = source[prop];
}
}
});
return obj;
},
// #### fetchJSONResource
// Fetch an HTTP resource with JSON representation, parse the JSON and
// return a JS object.
fetchJSONResource: function (url, opts) {
opts = MDN.defaults(opts || {}, {
headers: { 'Cache-Control': env.cache_control,
'Accept': 'application/json',
'Content-Type': 'application/json' },
});
return JSON.parse(MDN.fetchHTTPResource(url, opts));
},
// #### fetchHTTPResource
// Fetch an HTTP resource, return the response body.
fetchHTTPResource: function (url, opts) {
opts = MDN.defaults(opts || {}, {
method: 'GET',
headers: { 'Cache-Control': env.cache_control,
'Accept': "text/plain",
'Content-Type': "text/plain" },
url: url,
cache_key: 'kuma:http_resource:' + md5(url.toLowerCase()),
cache_timeout: 3600,
ignore_cache_control: false
});
var to_cache = function (next) {
try {
var req = request.get(opts);
req.on('response', function(res) {
var chunks = [];
var content_length = 0;
res.on('error', function(chunk) {
next(null);
});
res.on('data', function(chunk) {
chunks.push(chunk);
content_length += chunk.length;
});
res.on('end', function() {
var buffer = new Buffer(content_length);
var offset = 0;
for (var i=0; i<chunks.length; i++) {
chunks[i].copy(buffer, offset);
offset += chunks[i].length;
}
var encoding = res.headers['content-encoding'];
if (encoding == 'gzip') {
zlib.gunzip(buffer, function(err, decoded) {
next(decoded && decoded.toString());
});
} else if (encoding == 'deflate') {
zlib.inflate(buffer, function(err, decoded) {
next(decoded && decoded.toString());
})
} else {
next(buffer.toString());
}
});
});
req.on('error', function(err) { next(null); });
} catch (e) {
next(null);
}
};
if (opts.ignore_cache_control) {
return MDN.cacheFnIgnoreCacheControl(opts.cache_key, opts.cache_timeout, to_cache);
} else {
return cacheFn(opts.cache_key, opts.cache_timeout, to_cache);
}
},
/* Returns the appropriate string (usually a comma+space) for usage in a enumeration list for the current locale */
listSeparator: function() {
var listSeparators = { "en-US": ", ", "ar": "،", "fa": "،", "ja": "", "ko": "·", "mn": "", "ur": "،", "zh−TW": "" };
return mdn.localString(listSeparators);
},
/*
* FILE READING
*
* Utilities to read various files.
*/
CSVToArray: function( strData, strDelimiter ) {
// Check to see if the delimiter is defined. If not,
// then default to comma.
strDelimiter = (strDelimiter || ",");
// Create a regular expression to parse the CSV values.
var objPattern = new RegExp(
(
// Delimiters.
"(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
// Quoted fields.
"(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
// Standard fields.
"([^\"\\" + strDelimiter + "\\r\\n]*))"
),
"gi"
);
// Create an array to hold our data. Give the array
// a default empty first row.
var arrData = [[]];
// Create an array to hold our individual pattern
// matching groups.
var arrMatches = null;
// Keep looping over the regular expression matches
// until we can no longer find a match.
while (arrMatches = objPattern.exec( strData )){
// Get the delimiter that was found.
var strMatchedDelimiter = arrMatches[ 1 ];
// Check to see if the given delimiter has a length
// (is not the start of string) and if it matches
// field delimiter. If id does not, then we know
// that this delimiter is a row delimiter.
if (
strMatchedDelimiter.length &&
(strMatchedDelimiter != strDelimiter)
){
// Since we have reached a new row of data,
// add an empty row to our data array.
arrData.push( [] );
}
// Now that we have our delimiter out of the way,
// let's check to see which kind of value we
// captured (quoted or unquoted).
if (arrMatches[ 2 ]){
// We found a quoted value. When we capture
// this value, unescape any double quotes.
var strMatchedValue = arrMatches[ 2 ].replace(
new RegExp( "\"\"", "g" ),
"\""
);
} else {
// We found a non-quoted value.
var strMatchedValue = arrMatches[ 3 ];
}
// Now that we have our value string, let's add
// it to the data array.
arrData[ arrData.length - 1 ].push( strMatchedValue );
}
// Return the parsed data.
return( arrData );
},
loadArrayFromCSV: function(url, separator) {
url = url.replace(/&amp;/i, "&");
var html = MDN.fetchHTTPResource(url, {
headers: { 'Cache-Control': env.cache_control,
'Accept': 'text/csv',
'Content-Type': 'text/csv' },
});
return MDN.CSVToArray(html, separator);
},
/* http://www.bugzilla.org/docs/4.2/en/html/api/Bugzilla/WebService/Bug.html#search */
bzSearch: function (query) {
/* Fix colon (":") encoding problems */
query = query.replace(/&amp;/g, "&");
query = encodeURI(query);
query = query.replace(/&/g, "%26");
var url = 'https://bugzilla.mozilla.org/jsonrpc.cgi?method=Bug.search&params=' + query;
var resource = MDN.fetchJSONResource(url);
return resource.result;
},
/* Derive the site URL from the request URL */
siteURL: function () {
var p = kuma.url.parse(env.url, true),
site_url = p.protocol + '//' + p.host;
return site_url;
}
} %>