2014-08-15 07:13:35 -04:00
|
|
|
async = require "async"
|
|
|
|
_ = require "underscore"
|
2015-03-04 11:43:59 -05:00
|
|
|
ASpellWorkerPool = require "./ASpellWorkerPool"
|
|
|
|
LRU = require "lru-cache"
|
2015-03-09 11:57:39 -04:00
|
|
|
logger = require 'logger-sharelatex'
|
2014-08-15 07:13:35 -04:00
|
|
|
|
2015-03-04 11:43:59 -05:00
|
|
|
cache = LRU(10000)
|
2014-08-15 07:13:35 -04:00
|
|
|
|
|
|
|
class ASpellRunner
|
|
|
|
checkWords: (language, words, callback = (error, result) ->) ->
|
|
|
|
@runAspellOnWords language, words, (error, output) =>
|
|
|
|
return callback(error) if error?
|
2015-03-04 11:43:59 -05:00
|
|
|
#output = @removeAspellHeader(output)
|
2014-08-15 07:13:35 -04:00
|
|
|
suggestions = @getSuggestions(output)
|
|
|
|
results = []
|
2015-03-09 11:57:39 -04:00
|
|
|
hits = 0
|
2014-08-15 07:13:35 -04:00
|
|
|
for word, i in words
|
2015-03-04 12:00:19 -05:00
|
|
|
key = language + ':' + word
|
|
|
|
cached = cache.get(key)
|
|
|
|
if cached?
|
2015-03-09 11:57:39 -04:00
|
|
|
hits++
|
|
|
|
if cached == true
|
|
|
|
# valid word, no need to do anything
|
|
|
|
continue
|
|
|
|
else
|
|
|
|
results.push index: i, suggestions: cached
|
|
|
|
else
|
|
|
|
if suggestions[word]?
|
|
|
|
cache.set(key, suggestions[word])
|
|
|
|
results.push index: i, suggestions: suggestions[word]
|
|
|
|
else
|
|
|
|
# a valid word, but uncached
|
|
|
|
cache.set(key, true)
|
|
|
|
logger.log hits: hits, total: words.length, hitrate: hits/words.length, "cache hit rate"
|
2014-08-15 07:13:35 -04:00
|
|
|
callback null, results
|
|
|
|
|
|
|
|
getSuggestions: (output) ->
|
|
|
|
lines = output.split("\n")
|
|
|
|
suggestions = {}
|
|
|
|
for line in lines
|
|
|
|
if line[0] == "&" # Suggestions found
|
|
|
|
parts = line.split(" ")
|
|
|
|
if parts.length > 1
|
|
|
|
word = parts[1]
|
|
|
|
suggestionsString = line.slice(line.indexOf(":") + 2)
|
|
|
|
suggestions[word] = suggestionsString.split(", ")
|
|
|
|
else if line[0] == "#" # No suggestions
|
|
|
|
parts = line.split(" ")
|
|
|
|
if parts.length > 1
|
|
|
|
word = parts[1]
|
|
|
|
suggestions[word] = []
|
|
|
|
return suggestions
|
|
|
|
|
2015-03-04 11:43:59 -05:00
|
|
|
#removeAspellHeader: (output) -> output.slice(1)
|
2014-08-15 07:13:35 -04:00
|
|
|
|
2015-03-04 11:43:59 -05:00
|
|
|
runAspellOnWords: (language, words, callback = (error, output) ->) ->
|
|
|
|
# send words to aspell, get back string output for those words
|
|
|
|
# find a free pipe for the language (or start one)
|
|
|
|
# send the words down the pipe
|
|
|
|
# send an END marker that will generate a "*" line in the output
|
|
|
|
# when the output pipe receives the "*" return the data sofar and reset the pipe to be available
|
|
|
|
#
|
|
|
|
# @open(language)
|
|
|
|
# @captureOutput(callback)
|
|
|
|
# @setTerseMode()
|
|
|
|
# start = new Date()
|
2015-03-02 11:58:10 -05:00
|
|
|
|
2015-03-04 11:43:59 -05:00
|
|
|
newWord = {}
|
|
|
|
for word in words
|
2015-03-04 12:00:19 -05:00
|
|
|
newWord[word] = true if !newWord[word] && !cache.has(language + ':' + word)
|
2015-03-04 11:43:59 -05:00
|
|
|
words = Object.keys(newWord)
|
2015-03-02 11:58:10 -05:00
|
|
|
|
2015-03-04 11:43:59 -05:00
|
|
|
if words.length
|
|
|
|
WorkerPool.check(language, words, ASpell.ASPELL_TIMEOUT, callback)
|
|
|
|
else
|
2015-03-04 12:00:19 -05:00
|
|
|
callback null, ""
|
2014-08-15 07:13:35 -04:00
|
|
|
|
|
|
|
module.exports = ASpell =
|
|
|
|
# The description of how to call aspell from another program can be found here:
|
|
|
|
# http://aspell.net/man-html/Through-A-Pipe.html
|
|
|
|
checkWords: (language, words, callback = (error, result) ->) ->
|
|
|
|
runner = new ASpellRunner()
|
|
|
|
callback = _.once callback
|
|
|
|
runner.checkWords language, words, callback
|
|
|
|
ASPELL_TIMEOUT : 4000
|
2015-03-04 11:43:59 -05:00
|
|
|
|
|
|
|
WorkerPool = new ASpellWorkerPool()
|