UNPKG

5.08 kBJavaScriptView Raw
1 // parses unified diff
2 // http://www.gnu.org/software/diffutils/manual/diffutils.html#Unified-Format
3var defaultToWhiteSpace, escapeRegExp, ltrim, makeString, parseFile, parseFileFallback, trimLeft,
4 slice = [].slice;
5
6module.exports = function(input) {
7 var add, chunk, current, del, deleted_file, eof, file, files, from_file, index, j, len, line, lines, ln_add, ln_del, new_file, normal, parse, restart, schema, start, to_file;
8 if (!input) {
9 return [];
10 }
11 if (input.match(/^\s+$/)) {
12 return [];
13 }
14 lines = input.split('\n');
15 if (lines.length === 0) {
16 return [];
17 }
18 files = [];
19 file = null;
20 ln_del = 0;
21 ln_add = 0;
22 current = null;
23 start = function(line) {
24 var fileNames;
25 file = {
26 chunks: [],
27 deletions: 0,
28 additions: 0
29 };
30 files.push(file);
31 if (!file.to && !file.from) {
32 fileNames = parseFile(line);
33 if (fileNames) {
34 file.from = fileNames[0];
35 return file.to = fileNames[1];
36 }
37 }
38 };
39 restart = function() {
40 if (!file || file.chunks.length) {
41 return start();
42 }
43 };
44 new_file = function() {
45 restart();
46 file.new = true;
47 return file.from = '/dev/null';
48 };
49 deleted_file = function() {
50 restart();
51 file.deleted = true;
52 return file.to = '/dev/null';
53 };
54 index = function(line) {
55 restart();
56 return file.index = line.split(' ').slice(1);
57 };
58 from_file = function(line) {
59 restart();
60 return file.from = parseFileFallback(line);
61 };
62 to_file = function(line) {
63 restart();
64 return file.to = parseFileFallback(line);
65 };
66 chunk = function(line, match) {
67 var newLines, newStart, oldLines, oldStart;
68 ln_del = oldStart = +match[1];
69 oldLines = +(match[2] || 0);
70 ln_add = newStart = +match[3];
71 newLines = +(match[4] || 0);
72 current = {
73 content: line,
74 changes: [],
75 oldStart,
76 oldLines,
77 newStart,
78 newLines
79 };
80 return file.chunks.push(current);
81 };
82 del = function(line) {
83 if (!current) {
84 return;
85 }
86 current.changes.push({
87 type: 'del',
88 del: true,
89 ln: ln_del++,
90 content: line
91 });
92 return file.deletions++;
93 };
94 add = function(line) {
95 if (!current) {
96 return;
97 }
98 current.changes.push({
99 type: 'add',
100 add: true,
101 ln: ln_add++,
102 content: line
103 });
104 return file.additions++;
105 };
106 normal = function(line) {
107 if (!current) {
108 return;
109 }
110 return current.changes.push({
111 type: 'normal',
112 normal: true,
113 ln1: ln_del++,
114 ln2: ln_add++,
115 content: line
116 });
117 };
118 eof = function(line) {
119 var recentChange, ref;
120 ref = current.changes, [recentChange] = slice.call(ref, -1);
121 return current.changes.push({
122 type: recentChange.type,
123 [`${recentChange.type}`]: true,
124 ln1: recentChange.ln1,
125 ln2: recentChange.ln2,
126 ln: recentChange.ln,
127 content: line
128 });
129 };
130 // todo beter regexp to avoid detect normal line starting with diff
131 schema = [[/^\s+/, normal], [/^diff\s/, start], [/^new file mode \d+$/, new_file], [/^deleted file mode \d+$/, deleted_file], [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index], [/^---\s/, from_file], [/^\+\+\+\s/, to_file], [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk], [/^-/, del], [/^\+/, add], [/^\\ No newline at end of file$/, eof]];
132 parse = function(line) {
133 var j, len, m, p;
134 for (j = 0, len = schema.length; j < len; j++) {
135 p = schema[j];
136 m = line.match(p[0]);
137 if (m) {
138 p[1](line, m);
139 return true;
140 }
141 }
142 return false;
143 };
144 for (j = 0, len = lines.length; j < len; j++) {
145 line = lines[j];
146 parse(line);
147 }
148 return files;
149};
150
151parseFile = function(s) {
152 var fileNames;
153 if (!s) {
154 return;
155 }
156 fileNames = s.match(/a\/.*(?= b)|b\/.*$/g);
157 fileNames.map(function(fileName, i) {
158 return fileNames[i] = fileName.replace(/^(a|b)\//, '');
159 });
160 return fileNames;
161};
162
163// fallback function to overwrite file.from and file.to if executed
164parseFileFallback = function(s) {
165 var t;
166 s = ltrim(s, '-');
167 s = ltrim(s, '+');
168 s = s.trim();
169 // ignore possible time stamp
170 t = /\t.*|\d{4}-\d\d-\d\d\s\d\d:\d\d:\d\d(.\d+)?\s(\+|-)\d\d\d\d/.exec(s);
171 if (t) {
172 s = s.substring(0, t.index).trim();
173 }
174 // ignore git prefixes a/ or b/
175 if (s.match(/^(a|b)\//)) {
176 return s.substr(2);
177 } else {
178 return s;
179 }
180};
181
182ltrim = function(s, chars) {
183 s = makeString(s);
184 if (!chars && trimLeft) {
185 return trimLeft.call(s);
186 }
187 chars = defaultToWhiteSpace(chars);
188 return s.replace(new RegExp('^' + chars + '+'), '');
189};
190
191makeString = function(s) {
192 if (s === null) {
193 return '';
194 } else {
195 return s + '';
196 }
197};
198
199trimLeft = String.prototype.trimLeft;
200
201defaultToWhiteSpace = function(chars) {
202 if (chars === null) {
203 return '\\s';
204 }
205 if (chars.source) {
206 return chars.source;
207 }
208 return '[' + escapeRegExp(chars) + ']';
209};
210
211escapeRegExp = function(s) {
212 return makeString(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
213};