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 | current.changes.push {type:'del', del:true, ln:ln_del++, content:line}
|
69 | file.deletions++
|
70 |
|
71 | add = (line) ->
|
72 | current.changes.push {type:'add', add:true, ln:ln_add++, content:line}
|
73 | file.additions++
|
74 |
|
75 | normal = (line) ->
|
76 | current.changes.push {
|
77 | type: 'normal'
|
78 | normal: true
|
79 | ln1: ln_del++
|
80 | ln2: ln_add++
|
81 | content: line
|
82 | }
|
83 |
|
84 | eof = (line) ->
|
85 | [..., recentChange] = current.changes
|
86 |
|
87 | current.changes.push {
|
88 | type: recentChange.type
|
89 | "#{recentChange.type}": true
|
90 | ln1: recentChange.ln1
|
91 | ln2: recentChange.ln2
|
92 | ln: recentChange.ln
|
93 | content: line
|
94 | }
|
95 |
|
96 | schema = [
|
97 |
|
98 | [/^\s+/, normal],
|
99 | [/^diff\s/, start],
|
100 | [/^new file mode \d+$/, new_file],
|
101 | [/^deleted file mode \d+$/, deleted_file],
|
102 | [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index],
|
103 | [/^---\s/, from_file],
|
104 | [/^\+\+\+\s/, to_file],
|
105 | [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk],
|
106 | [/^-/, del],
|
107 | [/^\+/, add],
|
108 | [/^\\ No newline at end of file$/, eof]
|
109 | ]
|
110 |
|
111 | parse = (line) ->
|
112 | for p in schema
|
113 | m = line.match p[0]
|
114 | if m
|
115 | p[1](line, m)
|
116 | return true
|
117 | return false
|
118 |
|
119 | for line in lines
|
120 | parse line
|
121 |
|
122 | return files
|
123 |
|
124 | parseFile = (s) ->
|
125 | return if not s
|
126 |
|
127 | fileNames = s.split(' ').slice(-2)
|
128 | fileNames.map (fileName, i) ->
|
129 | fileNames[i] = fileName.replace(/^(a|b)\//, '')
|
130 |
|
131 | return fileNames
|
132 |
|
133 |
|
134 | parseFileFallback = (s) ->
|
135 | s = ltrim s, '-'
|
136 | s = ltrim s, '+'
|
137 | s = s.trim()
|
138 |
|
139 | t = (/\t.*|\d{4}-\d\d-\d\d\s\d\d:\d\d:\d\d(.\d+)?\s(\+|-)\d\d\d\d/).exec(s)
|
140 | s = s.substring(0, t.index).trim() if t
|
141 |
|
142 | if s.match (/^(a|b)\//) then s.substr(2) else s
|
143 |
|
144 | ltrim = (s, chars) ->
|
145 | s = makeString(s)
|
146 | return trimLeft.call(s) if !chars and trimLeft
|
147 | chars = defaultToWhiteSpace(chars)
|
148 | return s.replace(new RegExp('^' + chars + '+'), '')
|
149 |
|
150 | makeString = (s) -> if s == null then '' else s + ''
|
151 |
|
152 | trimLeft = String.prototype.trimLeft
|
153 |
|
154 | defaultToWhiteSpace = (chars) ->
|
155 | return '\\s' if chars == null
|
156 | return chars.source if chars.source
|
157 | return '[' + escapeRegExp(chars) + ']'
|
158 |
|
159 | escapeRegExp = (s) ->
|
160 | makeString(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1')
|