| var fs = require('fs'), |
| child_process = require('child_process'), |
| temp = require('temp'), |
| path = require('path'), |
| logger = require('./logger').logger, |
| Promise = require('promise') // jshint ignore:line |
| ; |
| |
| function cleanAndGetIndexes(text) { |
| var addRules = { |
| name: "add", |
| openTag: "{+", |
| replaceOpenTag: "", |
| closeTag: "+}", |
| replaceCloseTag: "" |
| }; |
| var delRules = { |
| name: "del", |
| openTag: "[-", |
| replaceOpenTag: "", |
| closeTag: "-]", |
| replaceCloseTag: "" |
| }; |
| var rules = [addRules, delRules]; |
| |
| var TagTypeEnum = { |
| OPENING: 1, |
| CLOSING: 2 |
| }; |
| |
| function tagLookup(rules, text, pos) { |
| var seen = false; |
| var type = null; |
| var rule = null; |
| for (var i = 0; i < rules.length; i++) { |
| var candidateTag = text.slice(pos, pos + rules[i].openTag.length); |
| if (rules[i].openTag == candidateTag) { |
| seen = true; |
| type = TagTypeEnum.OPENING; |
| rule = i; |
| break; |
| } |
| candidateTag = text.slice(pos, pos + rules[i].closeTag.length); |
| if (rules[i].closeTag == candidateTag) { |
| seen = true; |
| type = TagTypeEnum.CLOSING; |
| rule = i; |
| break; |
| } |
| } |
| |
| return { |
| seen: seen, |
| rule: rule, |
| type: type |
| }; |
| } |
| |
| var finalText = ""; |
| var posInFinalText = 0; // character that is going to be treated |
| // The position in the original text: |
| var posInText = 0; // character that is going to be treated |
| var StateEnum = { |
| OUTSIDE_TAG: 1, |
| INSIDE_TAG: 2 |
| }; |
| var state = StateEnum.OUTSIDE_TAG; |
| var zones = [[], []]; |
| var currentTextBeginPos = 0; |
| var currentTextEndPos = null; |
| var currentTagBeginPos = null; |
| var currentTagEndPos = null; |
| var currentTag = null; |
| |
| function forward() { |
| posInFinalText = posInFinalText + 1; |
| posInText = posInText + 1; |
| } |
| |
| function seenOpeningTag() { |
| memorizeText(); |
| currentTagBeginPos = posInFinalText; |
| finalText = finalText.concat(rules[currentTag].replaceOpenTag); |
| posInFinalText = posInFinalText + rules[currentTag].replaceOpenTag.length; |
| posInText = posInText + rules[currentTag].openTag.length; |
| currentTextBeginPos = posInText; |
| } |
| |
| function seenClosingTag() { |
| memorizeText(); |
| finalText = finalText.concat(rules[currentTag].replaceCloseTag); |
| posInFinalText = posInFinalText + rules[currentTag].replaceCloseTag.length; |
| posInText = posInText + rules[currentTag].closeTag.length; |
| currentTagEndPos = posInFinalText - 1; |
| zones[currentTag].push({begin: currentTagBeginPos, end: currentTagEndPos}); |
| currentTextBeginPos = posInText; |
| } |
| |
| function memorizeText() { |
| currentTextEndPos = posInText - 1; |
| if (currentTextEndPos >= currentTextBeginPos) { |
| finalText = finalText.concat(text.slice(currentTextBeginPos, currentTextEndPos + 1)); |
| } |
| } |
| |
| function end() { |
| memorizeText(); |
| } |
| |
| while (posInText < text.length) { |
| var tag = tagLookup(rules, text, posInText); |
| if (tag.seen && tag.type == TagTypeEnum.OPENING) { |
| if (state != StateEnum.OUTSIDE_TAG) { |
| log.error("Opening tag while not outside tag (tags cannot be nested)"); |
| return null; |
| } |
| currentTag = tag.rule; |
| seenOpeningTag(); |
| state = StateEnum.INSIDE_TAG; |
| } else if (tag.seen && tag.type == TagTypeEnum.CLOSING) { |
| if (state != StateEnum.INSIDE_TAG) { |
| log.error("Closing tag while not inside tag."); |
| return null; |
| } |
| if (currentTag != tag.rule) { |
| log.error("Closing tag, but not of the same type as previously opened."); |
| return null; |
| } |
| seenClosingTag(); |
| state = StateEnum.OUTSIDE_TAG; |
| } else { |
| forward(); |
| } |
| } |
| end(); |
| |
| return {text: finalText, zones: zones}; |
| } |
| |
| function newTempDir() { |
| return new Promise(function (resolve, reject) { |
| temp.mkdir('compiler-explorer-diff', function (err, dirPath) { |
| if (err) |
| reject("Unable to open temp file: " + err); |
| else |
| resolve(dirPath); |
| }); |
| }); |
| } |
| |
| var writeFile = Promise.denodeify(fs.writeFile); |
| |
| function buildDiffHandler(config) { |
| var maxFileSize = config.maxOutput; |
| return function diffHandler(req, res) { |
| var before = req.body.before; |
| var after = req.body.after; |
| var error; |
| if (before === undefined) { |
| error = 'Warning : Bad request : wrong "before"'; |
| logger.error(error); |
| return next(new Error(error)); |
| } |
| if (after === undefined) { |
| error = 'Warning : Bad request : wrong "after"'; |
| logger.error(error); |
| return next(new Error(error)); |
| } |
| |
| var tempBeforePath, tempAfterPath; |
| newTempDir() |
| .then(function (tmpDir) { |
| tempBeforePath = path.join(tmpDir, "compiler-explorer-wdiff-before"); |
| tempAfterPath = path.join(tmpDir, "compiler-explorer-wdiff-after"); |
| return Promise.all( |
| writeFile(tempBeforePath, before), |
| writeFile(tempAfterPath, after) |
| ); |
| }) |
| .then(function () { |
| var truncated = false; |
| var stdout = ""; |
| var child = child_process.spawn(config.wdiffExe, [tempBeforePath, tempAfterPath], |
| {maxBuffer: maxFileSize, detached: true}); |
| child.stdout.on('data', function (data) { |
| if (truncated) return; |
| if (stdout.length > maxFileSize) { |
| stdout += "\n[Truncated]"; |
| truncated = true; |
| child.kill(); |
| resolve(stdout); |
| } |
| stdout += data; |
| }); |
| return new Promise(function (resolve, reject) { |
| child.on('error', function (e) { |
| reject(e); |
| }); |
| child.on('exit', function () { |
| // See comment in compile-handler.js - seems needed if the child has immediately exited |
| setTimeout(function () { |
| resolve(stdout); |
| }, 0); |
| }); |
| }); |
| }) |
| .then(function (output) { |
| var cleaned = cleanAndGetIndexes(output); |
| res.set('Content-Type', 'application/json'); |
| if (cleaned === null) { |
| res.end(JSON.stringify({ |
| computedDiff: "Failed to clean the diff", |
| zones: null |
| })); |
| } else { |
| res.end(JSON.stringify({ |
| computedDiff: cleaned.text, |
| zones: cleaned.zones |
| })); |
| } |
| }) |
| .catch(function (err) { |
| res.end(JSON.stringify({ |
| computedDiff: "Failed to diff: " + err, |
| zones: null |
| })); |
| }); |
| }; |
| } |
| |
| module.exports = { |
| buildDiffHandler: buildDiffHandler |
| }; |