grawlix
Version:
Replaces profanity with nonsensical symbols
384 lines (371 loc) • 12.4 kB
JavaScript
;
const _ = require('underscore');
const defaultFilters = require('./filters').filters;
const defaultStyles = require('./styles').styles;
const FilterSort = require('./filters').FilterSort;
const GrawlixFilter = require('./filters').GrawlixFilter;
const toGrawlixFilter = require('./filters').toGrawlixFilter;
const GrawlixStyle = require('./styles').GrawlixStyle;
const toGrawlixStyle = require('./styles').toGrawlixStyle;
const GrawlixStyleError = require('./styles').GrawlixStyleError;
const GrawlixPlugin = require('./plugin').GrawlixPlugin;
const GrawlixPluginError = require('./plugin').GrawlixPluginError;
/**
* Parse grawlix options
* @param {Object} options Options object. See grawlix.js#grawlix for
* details.
* @param {Object} defaults Default options. Optional.
* @return {GrawlixSettings}
*/
exports.parseOptions = function(options, defaults) {
if (!_.isUndefined(defaults)) {
_.defaults(options, defaults);
}
// get settings
var settings = new GrawlixSettings();
settings.isRandom = options.randomize;
// load default filters
_.each(defaultFilters, function(filter) {
// check to see if word is on whitelist
var isAllowed = _.contains(options.allowed, filter.word);
// check to see if options has a replacement filter
var isReplaced = _.some(options.filters, function(optFilter) {
return (
_.has(optFilter, 'word') &&
optFilter.word === filter.word &&
_.has(optFilter, 'pattern')
);
});
if (!isAllowed && !isReplaced) {
settings.filters.push( filter.clone() );
}
});
// load default styles
_.each(defaultStyles, function(style) {
settings.styles.push( style.clone() );
});
// load plugins (if we have any)
if (options.plugins.length > 0) {
_.each(options.plugins, function(pluginInfo) {
loadPlugin(settings, pluginInfo, options);
});
}
// add option filters (if any) and/or configure filter options
loadFilters(settings, options.filters, options.allowed);
// sort filters
settings.filters.sort(FilterSort);
// add option styles / configure style options
loadStyles(settings, options.styles);
// get main style
if (!_.has(options, 'style') || options.style === null) {
throw new GrawlixStyleError({
msg: 'main style not found',
style: options.style,
trace: new Error()
});
}
// try to find style
var style;
if (_.has(options.style, 'name')) {
// if options.style is a style object
style = _.findWhere(settings.styles, { name: options.style.name });
if (!_.isUndefined(style)) {
// if style is found, configure style with object
style.configure(options.style);
} else {
// if style not found, try to create a new style with object
style = toGrawlixStyle(options.style);
}
} else {
// try to treat options.style as string
style = _.findWhere(settings.styles, { name: options.style });
}
if (style instanceof GrawlixStyle) {
settings.style = style;
} else {
throw new GrawlixStyleError({
msg: 'main style not found',
styleName: options.style,
style: options.style,
trace: new Error()
});
}
// return settings
return settings;
};
/**
* GrawlixSettings class
* Class for settings object returned by parseOptions
*/
var GrawlixSettings = function() {
this.isRandom = true;
this.filters = [];
this.style = null;
this.styles = [];
this.loadedPlugins = [];
this.loadedModules = [];
};
GrawlixSettings.prototype = {};
exports.GrawlixSettings = GrawlixSettings;
/**
* Loads the given plugin into the given GrawlixSettings object
* @param {GrawlixSettings} settings GrawlixSettings object
* @param {Object|Function} pluginInfo Either a factory function, a
* GrawlixPlugin object, or an object
* wrapping a factory function or
* GrawlixPlugin with additional
* plugin-specific config options
* @param {Object} options Main grawlix options object
* @return {GrawlixSettings} Settings object with plugin loaded
*/
var loadPlugin = function(settings, pluginInfo, options) {
// resolve plugin and plugin options
var plugin;
if (_.has(pluginInfo, 'plugin')) {
plugin = pluginInfo.plugin;
} else if (_.has(pluginInfo, 'module')) {
// make sure we don't load the same module twice
if (_.contains(settings.loadedModules, pluginInfo.module)) {
return settings;
}
// try to load module
try {
plugin = require(pluginInfo.module);
} catch (err) {
throw new GrawlixPluginError({
msg: "cannot find module '" + pluginInfo.module + "'",
plugin: pluginInfo,
trace: new Error()
});
}
settings.loadedModules.push(pluginInfo.module);
} else {
plugin = pluginInfo;
}
var pluginOpts = _.has(pluginInfo, 'options') ? pluginInfo.options : {};
// instantiate plugin if necessary
if (_.isFunction(plugin)) {
plugin = plugin(pluginOpts, options);
}
// validate plugin
if (!(plugin instanceof GrawlixPlugin)) {
throw new GrawlixPluginError({
msg: 'invalid plugin',
plugin: pluginInfo
});
} else if (plugin.name === null || _.isEmpty(plugin.name)) {
throw new GrawlixPluginError({
msg: 'invalid plugin - name property not provided',
plugin: pluginInfo
});
} else if (_.contains(settings.loadedPlugins, plugin.name)) {
// don't load the same plugin twice
return settings;
}
// initialize plugin
plugin.init(pluginOpts);
// load filters
if (!_.isUndefined(plugin.filters) && _.isArray(plugin.filters)) {
try {
loadFilters(settings, plugin.filters, options.allowed);
} catch (err) {
err.plugin = pluginInfo;
throw err;
}
}
// load styles
if (!_.isUndefined(plugin.styles) && _.isArray(plugin.styles)) {
try {
loadStyles(settings, plugin.styles);
} catch (err) {
err.plugin = pluginInfo;
throw err;
}
}
// add name to loaded plugins
settings.loadedPlugins.push(plugin.name);
// return
return settings;
};
exports.loadPlugin = loadPlugin;
/**
* Loads an array of filter objects into the GrawlixSettings object
* @param {GrawlixSettings} settings GrawlixSettings object
* @param {Array} filters Array of filter objects
* @param {Array} allowed Whitelist of words to ignore
* @return {GrawlixSettings} Settings objects with filters added
*/
var loadFilters = function(settings, filters, allowed) {
if (filters.length > 0) {
_.each(filters, function(obj) {
if (!_.has(obj, 'word')) {
return;
}
if (!_.has(obj, 'pattern')) {
// configure existing filter options
var filter = _.findWhere(settings.filters, { word: obj.word });
if (!_.isUndefined(filter)) {
filter.configure(obj);
}
} else if (!_.contains(allowed, obj.word)) {
// if filter word isn't whitelisted, add as new GrawlixFilter
settings.filters.push( toGrawlixFilter(obj) );
}
});
}
return settings;
};
exports.loadFilters = loadFilters;
/**
* Loads an array of style objects into the given GrawlixSettings instance
* @param {GrawlixSettings} settings GrawlixSettings object
* @param {Array} styles Array of style objects
* @return {GrawlixSettings}
*/
var loadStyles = function(settings, styles) {
if (_.isArray(styles) && styles.length > 0) {
_.each(styles, function(obj) {
if (!_.has(obj, 'name')) {
return;
}
var style = _.findWhere(settings.styles, { name: obj.name });
if (!_.isUndefined(style)) {
style.configure(obj);
} else {
settings.styles.push( toGrawlixStyle(obj) );
}
});
}
return settings;
};
exports.loadStyles = loadStyles;
/**
* Returns whether or not the given plugin has been added to the given options
* object.
* @param {String|GrawlixPlugin|Function} plugin Plugin name, GrawlixPlugin
* object, or factory function.
* @param {Object} options Options object.
* @return {Boolean}
*/
exports.hasPlugin = function(plugin, options) {
if (_.has(options, 'plugins') && _.isArray(options.plugins)) {
// search for matching GrawlixPlugin
if (plugin instanceof GrawlixPlugin) {
return _.some(options.plugins, function(obj) {
return (
_.has(obj, 'plugin') &&
obj.plugin instanceof GrawlixPlugin &&
(obj.plugin === plugin || obj.plugin.name === plugin.name)
);
});
}
// search for matching factory function
if (_.isFunction(plugin)) {
return _.some(options.plugins, function(obj) {
return (_.has(obj, 'plugin') && obj.plugin === plugin);
});
}
// search by module and by GrawlixPlugin name
if (_.isString(plugin)) {
return _.some(options.plugins, function(obj) {
return (
(_.has(obj, 'module') && obj.module === plugin) ||
(
_.has(obj, 'plugin') &&
obj.plugin instanceof GrawlixPlugin &&
obj.plugin.name === plugin
)
);
});
}
}
// or, if all else fails...
return false;
};
/**
* Returns whether or not any of the given filters match the given string.
* @param {String} str Content string
* @param {GrawlixSettings} settings GrawlixSettings object
* @return {Boolean} Whether or not obscenity is found in the
* given string
*/
exports.isMatch = function(str, settings) {
if (settings.filters.length === 0) {
return false;
}
return _.some(settings.filters, function(filter) {
return filter.isMatch(str);
});
};
/**
* Replaces obscenities in the given string using the given settings.
* @param {String} str String to process
* @param {GrawlixSettings} settings Grawlix settings
* @return {String} Processed string
*/
exports.replaceMatches = function(str, settings) {
_.each(settings.filters, function(filter) {
while (filter.isMatch(str)) {
str = replaceMatch(str, filter, settings);
}
});
return str;
};
/**
* Replaces a filter match in a string
* @param {String} str Content string
* @param {GrawlixFilter} filter GrawlixFilter object
* @param {GrawlixSettings} settings GrawlixSettings object
* @return {String} String with replacement applied
*/
var replaceMatch = function(str, filter, settings) {
// get filter style if provided
var style;
if (filter.hasStyle() && settings.style.isOverrideAllowed) {
style = _.findWhere(settings.styles, { name: filter.style });
}
if (_.isUndefined(style)) {
// if filter style not found, or no filter style set, use main style
style = settings.style;
}
// get replacement grawlix
var repl;
if (!settings.isRandom && style.hasFixed(filter.word)) {
// if in fixed replacement mode and style has a defined fixed replacement
// string for the filter's word
repl = style.getFixed(filter.word);
} else {
// if single-character style
repl = generateGrawlix(str, filter, style);
}
// apply filter template if necessary
if (filter.hasTemplate()) {
repl = filter.template(repl);
}
// replace the match
return str.replace(filter.regex, repl);
};
exports.replaceMatch = replaceMatch;
/**
* Replaces matched content with a grawlix, taking into account filter and style
* settings.
* @param {String} str Content string
* @param {GrawlixFilter} filter Filter object
* @param {GrawlixStyle} style Style object
* @return {String} Grawlix replacement string
*/
var generateGrawlix = function(str, filter, style) {
// determine length
var len;
if (filter.isExpandable) {
len = filter.getMatchLen(str);
} else {
len = filter.word.length;
}
// generate grawlix
if (!style.canRandomize()) {
return style.getFillGrawlix(len);
}
return style.getRandomGrawlix(len);
};
exports.generateGrawlix = generateGrawlix;