UNPKG

pass-scene

Version:

Memorable passphrase generator

360 lines (314 loc) 9.47 kB
/** * wordpos.js * * Node.js part-of-speech utilities using natural's WordNet module. * * Copyright (c) 2012-2014 mooster@42at.com * https://github.com/moos/wordpos * * Released under MIT license */ var _ = require('underscore')._, util = require('util'), natural = require('natural'), WordNet = natural.WordNet, tokenizer = new natural.WordTokenizer(), natural_stopwords = makeStopwordString(natural.stopwords), WNdb = require('WNdb'), fastIndex = null; try { fastIndex = require('./fastIndex'); } catch(e) {} function normalize(word) { return word.toLowerCase().replace(/\s+/g, '_'); } function makeStopwordString(stopwords) { return ' '+ stopwords.join(' ') +' '; } function isStopword(stopwords, word) { return stopwords.indexOf(' '+word+' ') >= 0; } function prepText(text) { if (_.isArray(text)) return text; var deduped = _.uniq(tokenizer.tokenize(text)); if (!this.options.stopwords) return deduped; return _.reject(deduped, _.bind(isStopword, null, _.isString(this.options.stopwords) ? this.options.stopwords : natural_stopwords )); } function lookup(pos) { return function(word, callback) { var profile = this.options.profile, start = profile && new Date(), args = []; word = normalize(word); this.lookupFromFiles([ {index: this.getIndexFile(pos), data: this.getDataFile(pos)} ], [], word, function(results){ args.push(results, word); profile && args.push(new Date() - start); callback.apply(null, args); }); }; } function is(pos){ return function(word, callback, _noprofile) { // disable profiling when isX() used internally var profile = this.options.profile && !_noprofile, start = profile && new Date(), args = [], index = this.getIndexFile(pos); word = normalize(word); index.lookup(word, function(record) { args.push(!!record, word); profile && args.push(new Date() - start); callback.apply(null, args); }); }; } function rand(pos){ return function(opts, callback, _noprofile) { // disable profiling when isX() used internally var profile = this.options.profile && !_noprofile, start = profile && new Date(), args = [], index = this.getIndexFile(pos), startsWith = opts && opts.startsWith || '', count = opts && opts.count || 1; if (typeof opts === 'function') { callback = opts; } index.rand(startsWith, count, function(record) { args.push(record, startsWith); profile && args.push(new Date() - start); callback.apply(null, args); }); }; } function get(isFn) { return function(text, callback) { var profile = this.options.profile, start = profile && new Date(), words = this.parse(text), n = words.length, i = 0, self = this, results = [], args = [results], done = function(){ profile && (args[1] = new Date() - start); callback.apply(null, args) }; if (!n) return (process.nextTick(done),0); words.forEach(function(word,j){ self[isFn](word, function(yes){ yes && results.push(word); (++i==n) && done(); }, /*_noprofile*/ true); }); return n; }; } /** * @class WordPOS * @constructor */ var WordPOS = function(options) { if (arguments.length == 0 || _.isObject(options)) { WordPOS.super_.call(this, WNdb.path); } else { WordPOS.super_.apply(this, arguments); } this.options = _.defaults({}, _.isObject(options) && options || {}, WordPOS.defaults); if (this.options.fastIndex && fastIndex) { // override find this.nounIndex.find = fastIndex.find(this.nounIndex); this.verbIndex.find = fastIndex.find(this.verbIndex); this.adjIndex.find = fastIndex.find(this.adjIndex); this.advIndex.find = fastIndex.find(this.advIndex); // rand this.nounIndex.rand = fastIndex.rand(this.nounIndex); this.verbIndex.rand = fastIndex.rand(this.verbIndex); this.adjIndex.rand = fastIndex.rand(this.adjIndex); this.advIndex.rand = fastIndex.rand(this.advIndex); } if (_.isArray(this.options.stopwords)) { this.options.stopwords = makeStopwordString(this.options.stopwords); } }; util.inherits(WordPOS, WordNet); WordPOS.defaults = { /** * enable profiling, time in msec returned as second argument in callback */ profile: false, /** * use fast index if available */ fastIndex: true, /** * if true, exclude standard stopwords. * if array, stopwords to exclude, eg, ['all','of','this',...] * if false, do not filter any stopwords. */ stopwords: true }; var wordposProto = WordPOS.prototype; // fast POS lookups (only look in specified file) /** * lookupX() * Lookup word definition if already know POS * * @param string word - word to lookup in given POS * @param function callback receives array of definition objects or empty * @return none */ wordposProto.lookupAdjective = lookup('a'); wordposProto.lookupAdverb = lookup('r'); wordposProto.lookupNoun = lookup('n'); wordposProto.lookupVerb = lookup('v'); /** * isX() * Test if word is given POS * * @param string word - word to test for given POS * @param function Callback receives true or false if word is given POS * @return none */ wordposProto.isAdjective = is('a'); wordposProto.isAdverb = is('r'); wordposProto.isNoun = is('n'); wordposProto.isVerb = is('v'); /** * randX() */ wordposProto.randAdjective = rand('a'); wordposProto.randAdverb = rand('r'); wordposProto.randNoun = rand('n'); wordposProto.randVerb = rand('v'); /** * getX() * Find all words in string that are given POS * * @param string Text Words to search * @param function callback Receives array of words that are given POS * @return none */ wordposProto.getAdjectives = get('isAdjective'); wordposProto.getAdverbs = get('isAdverb'); wordposProto.getNouns = get('isNoun'); wordposProto.getVerbs = get('isVerb'); wordposProto.parse = prepText; if (!wordposProto.getIndexFile) { wordposProto.getIndexFile = function getIndexFile(pos) { switch(pos) { case 'n': return this.nounIndex; case 'v': return this.verbIndex; case 'a': case 's': return this.adjIndex; case 'r': return this.advIndex; } }; } /** * getPOS() * Find all POS for all words in given string * * @param {string} text - words to lookup for POS * @param {function} callback - receives object with words broken into POS or 'rest', ie, * Object: {nouns:[], verbs:[], adjectives:[], adverbs:[], rest:[]} * @return none */ wordposProto.getPOS = function(text, callback) { var data = {nouns:[], verbs:[], adjectives:[], adverbs:[], rest:[]}, profile = this.options.profile, start = profile && new Date(), args = [data], testFns = 'isNoun isVerb isAdjective isAdverb'.split(' '), parts = 'nouns verbs adjectives adverbs'.split(' '), words = this.parse(text), nTests = testFns.length, nWords = words.length, self = this, c = 0, done = function(){ profile && (args[1] = new Date() - start); callback.apply(null, args) }; if (!nWords) return (process.nextTick(done),0); words.forEach(lookup); function lookup(word){ var any = false, t=0; testFns.forEach(lookupPOS); function lookupPOS(isFn,i,list){ self[isFn](word, function(yes){ yes && data[parts[i]].push(word); any |= yes; donePOS(); }); } function donePOS() { if (++t == nTests) { !any && data['rest'].push(word); (++c == nWords) && done(); } } } return nWords; }; /** * rand() */ wordposProto.rand = function(opts, callback) { var profile = this.options.profile, start = profile && new Date(), results = [], startsWith = opts && opts.startsWith || '', count = opts && opts.count || 1, args = [null, startsWith], parts = 'Noun Verb Adjective Adverb'.split(' '), self = this, done = function(){ profile && (args.push(new Date() - start)); args[0] = results; callback.apply(null, args) }; if (typeof opts === 'function') { callback = opts; } else { opts = _.clone(opts); } // TODO -- or loop count times each time getting 1 from random part!! // slower but more random. // select at random a part to look at var doParts = _.sample(parts, parts.length); tryPart(); function tryPart(){ var rand = 'rand' + doParts.pop(); self[ rand ](opts, partCallback); } function partCallback(result){ if (result) { results = _.uniq(results.concat(result)); // make sure it's unique! } //console.log(result); if (results.length < count && doParts.length) { // reduce count for next part -- NO! may get duplicates // opts.count = count - results.length; return tryPart(); } // trim excess if (results.length > count) { results.length = count; } done(); } }; WordPOS.WNdb = WNdb; WordPOS.natural = natural; module.exports = WordPOS;