1 |
|
2 |
|
3 | var defaultToWhiteSpace, escapeRegExp, ltrim, makeString, parseFile, parseFileFallback, trimLeft,
|
4 | slice = [].slice;
|
5 |
|
6 | module.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 |
|
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 |
|
151 | parseFile = 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 |
|
164 | parseFileFallback = function(s) {
|
165 | var t;
|
166 | s = ltrim(s, '-');
|
167 | s = ltrim(s, '+');
|
168 | s = s.trim();
|
169 |
|
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 |
|
175 | if (s.match(/^(a|b)\//)) {
|
176 | return s.substr(2);
|
177 | } else {
|
178 | return s;
|
179 | }
|
180 | };
|
181 |
|
182 | ltrim = 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 |
|
191 | makeString = function(s) {
|
192 | if (s === null) {
|
193 | return '';
|
194 | } else {
|
195 | return s + '';
|
196 | }
|
197 | };
|
198 |
|
199 | trimLeft = String.prototype.trimLeft;
|
200 |
|
201 | defaultToWhiteSpace = 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 |
|
211 | escapeRegExp = function(s) {
|
212 | return makeString(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
|
213 | };
|