overleaf/services/spelling/app/coffee/ASpell.coffee

108 lines
2.9 KiB
CoffeeScript
Raw Normal View History

2014-08-15 07:13:35 -04:00
child_process = require("child_process")
async = require "async"
_ = require "underscore"
class ASpellRunner
checkWords: (language, words, callback = (error, result) ->) ->
@runAspellOnWords language, words, (error, output) =>
return callback(error) if error?
output = @removeAspellHeader(output)
suggestions = @getSuggestions(output)
results = []
for word, i in words
if suggestions[word]?
results.push index: i, suggestions: suggestions[word]
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
removeAspellHeader: (output) -> output.slice(1)
runAspellOnWords: (language, words, callback = (error, output) ->) ->
@open(language)
@captureOutput(callback)
@setTerseMode()
start = new Date()
i = 0
do tick = () =>
if new Date() - start > ASpell.ASPELL_TIMEOUT
@close(true)
else if i < words.length
# batch up the words to check for efficiency
batch = words.slice(i, i + ASpell.ASPELL_BATCH_SIZE)
@sendWords(batch)
i += ASpell.ASPELL_BATCH_SIZE
2014-08-15 07:25:54 -04:00
setTimeout tick, 0
2014-08-15 07:13:35 -04:00
else
@close()
captureOutput: (callback = (error, output) ->) ->
output = ""
error = ""
@aspell.stdout.on "data", (chunk) ->
output = output + chunk
@aspell.stderr.on "data", (chunk) =>
error = error + chunk
@aspell.stdout.on "end", () ->
if error == ""
callback null, output
else
callback new Error(error), output
open: (language) ->
@finished = false
@aspell = child_process.spawn("aspell", ["pipe", "-t", "--encoding=utf-8", "-d", language])
close: (force) ->
@finished = true
@aspell.stdin.end()
if force && !@aspell.exitCode?
@aspell.kill("SIGKILL")
setTerseMode: () ->
@sendCommand("!")
sendWord: (word) ->
@sendCommand("^" + word)
sendWords: (words) ->
# Aspell accepts multiple words to check on the same line
# ^word1 word2 word3 ...
# See aspell.info, writing programs to use Aspell Through A Pipe
@sendCommand("^" + words.join(" "))
2014-08-15 07:13:35 -04:00
sendCommand: (command) ->
@aspell.stdin.write(command + "\n")
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
forceClose = ->
runner.close(true)
callback("process killed")
setTimeout forceClose, @ASPELL_TIMEOUT
ASPELL_TIMEOUT : 4000
ASPELL_BATCH_SIZE : 100