UNPKG

differ-cli

Version:

differ realized by javascript in cli

351 lines (297 loc) 9.15 kB
var compare = require('universal-diff').compare; var clc = require('cli-color'); var config = require('./config'); var isSimilar = require('./similarity').judge; // make string with given length var toLen = function(source, len){ return (source + '').length >= len ? source : (Array.prototype.join.call({length:len + 1}, ' ') + source).slice(-len); }; // simple split var simpleSplit = function(cnt){ return cnt.split(''); }; // participle code / word var participle = function(cnt){ if(cnt.length <= 1) return cnt.split(''); var arr = []; for(var i = 1, l = cnt.length, pos = 0; i < l; i++){ var isPlainPattern = /[\w\_\$]+/, isPrevPlain = isPlainPattern.test(cnt[i-1]), isPlain = isPlainPattern.test(cnt[i]); if( !(isPrevPlain && isPlain) && !(cnt[i-1] === ' ' && cnt[i] === ' ') ){ arr.push(cnt.slice(pos, i)); pos = i; } if(i === l-1){ arr.push(cnt.slice(pos)); } } return arr; }; // method to wrap line number var wrapNum = function(num){ return toLen(num, 5) + ' |'; }; // header of lines in different lines var header = { normal: function(line){ return ['|', wrapNum(line.pos1), wrapNum(line.pos2), ' ']; }, remove: function(line){ return ['|', wrapNum(line.pos1), wrapNum(''), '-']; }, insert: function(line){ return ['|', wrapNum(''), wrapNum(line.pos2), '+']; } }; // effects of lines in different lines var effect = { line: { normal: function(cnt){ return cnt; }, remove: function(cnt){ return clc.red(cnt); }, insert: function(cnt){ return clc.green(cnt); } }, word: { normal: function(cnt){ return cnt; }, remove: function(cnt){ return clc.bgRed.white(cnt); }, insert: function(cnt){ return clc.bgGreen.black(cnt); } } }; // wrap a word var getWord = function(word){ return effect.word[word.type](word.cnt); }; // wrap one line var getLine = function(line){ var transform = effect.line[line.type]; var cnt = line.cnt.map(function(word){ return transform(getWord(word)); }).join(''); var headerParts = header[line.type](line).map(function(part){ return transform(part); }); return ' ' + headerParts.concat(cnt).join(' '); }; // wrap a block var getBlock = function(block){ return block.lines.map(getLine).join('\n'); }; // diff by lines var diffByLines = function(cnt1, cnt2){ var lines1 = cnt1.split('\n'), lines2 = cnt2.split('\n'), lines = []; var curr = { pos1: 0, pos2: 0, prevEnd: 0 }; // deal with diffs compare(lines1, lines2).forEach(function(diff, i){ var start = diff[0], len = diff[1], to = diff[2]; lines1.slice(curr.prevEnd, start).forEach(function(cnt){ lines.push({ type: 'normal', cnt: cnt, pos1: ++curr.pos1, pos2: ++curr.pos2 }); }); lines1.slice(start, start + len).forEach(function(cnt){ lines.push({ type: 'remove', cnt: cnt, pos1: ++curr.pos1, pos2: '' }); }); to && to.forEach(function(cnt){ lines.push({ type: 'insert', cnt: cnt, pos1: '', pos2: ++curr.pos2 }); }); curr.prevEnd = start + len; }); // same content after the last diff lines1.slice(curr.prevEnd).forEach(function(cnt){ lines.push({ type: 'normal', cnt: cnt, pos1: ++curr.pos1, pos2: ++curr.pos2 }); }); return lines; }; // diff by words var diffByWords = function(line1, line2){ if( line1.cnt.length >= config.lengthLimit || line2.cnt.length >= config.lengthLimit ){ return; } var prevEnd = 0, origin = line1.participledCnt || participle(line1.cnt), target = line2.participledCnt || participle(line2.cnt), originArr = [], targetArr = []; compare(origin, target).forEach(function(diff, i){ var start = diff[0], len = diff[1], to = diff[2]; var kept = origin.slice(prevEnd, start).join(''); if(kept){ kept = { type: 'normal', cnt: kept }; originArr.push(kept); targetArr.push(kept); } var removed = origin.slice(start, start + len).join(''); if(removed){ removed = { type: 'remove', cnt: removed }; originArr.push(removed); } var inserted = to; if(inserted){ inserted = { type: 'insert', cnt: inserted.join('') }; targetArr.push(inserted); } prevEnd = start + len; }); var leftKept = origin.slice(prevEnd).join(''); if(leftKept){ leftKept = { type: 'normal', cnt: leftKept }; originArr.push(leftKept); targetArr.push(leftKept); } line1.cnt = originArr; line2.cnt = targetArr; }; // diff in line (between words) var diffInline = function(lines){ lines.forEach(function(line, i){ var curr = lines[i], next = lines[i+1]; if( (curr.type === 'remove') && (next && next.type === 'insert') ){ var removeNum = 0, insertNum = 0; while(curr && curr.type === 'remove'){ removeNum++; curr = lines[i - removeNum]; } while(next && next.type === 'insert'){ insertNum++; next = lines[i + 1 + insertNum]; } if(!config.similarityJudge){ // simple judge if(removeNum === insertNum){ for(var j = 0; j < insertNum; j++){ diffByWords(lines[i - removeNum + 1 + j], lines[i + 1 + j]); } } }else{ // judge based on similarity var removed = lines.slice(i - removeNum + 1, i + 1), inserted = lines.slice(i + 1, i + insertNum + 1); var j = 0; removed.forEach(function(removedLine){ for(var insertedLine; j < insertNum; j++){ insertedLine = inserted[j]; if(insertedLine){ var removedCnt = removedLine.participledCnt = participle(removedLine.cnt), insertedCnt = insertedLine.participledCnt = participle(insertedLine.cnt); if(insertedLine && isSimilar(removedCnt, insertedCnt)){ diffByWords(removedLine, insertedLine); inserted[j] = null; break; } } } }); } } }); lines.forEach(function(line){ if(typeof line.cnt === 'string'){ line.cnt = [{ type: 'normal', cnt: line.cnt }]; } }); return lines; }; // get diff output between two cnts var getOutput = function(cnt1, cnt2){ var linesDiff = diffInline(diffByLines(cnt1, cnt2)); var curr, blocks = []; for(var i = 0, l = linesDiff.length, line, start, end; i < l; i++){ line = linesDiff[i]; if(line.type !== 'normal'){ if(curr && i <= (curr.end + config.aroundLineNum)){ curr.end = Math.min(l - 1, i + config.aroundLineNum); }else{ if(curr){ //console.log(curr); blocks.push({ start: linesDiff[curr.start].pos1, end: linesDiff[curr.end].pos1, lines: linesDiff.slice(curr.start, curr.end + 1) }); } curr = { start: Math.max(0, i - config.aroundLineNum), end: Math.min(l - 1, i + config.aroundLineNum) }; } } if(i === l - 1 && curr){ blocks.push({ start: linesDiff[curr.start].pos1, end: linesDiff[curr.end].pos1, lines: linesDiff.slice(curr.start, curr.end + 1) }); } } var splitter = '\n\n ...\n\n'; return blocks.length ? splitter + blocks.map(getBlock).join(splitter) + splitter : '> No change.'; }; module.exports = getOutput;