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