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