UNPKG

3.66 kBtext/coffeescriptView Raw
1# parses unified diff
2# http://www.gnu.org/software/diffutils/manual/diffutils.html#Unified-Format
3module.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 # todo beter regexp to avoid detect normal line starting with diff
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
124parseFile = (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# fallback function to overwrite file.from and file.to if executed
134parseFileFallback = (s) ->
135 s = ltrim s, '-'
136 s = ltrim s, '+'
137 s = s.trim()
138 # ignore possible time stamp
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 # ignore git prefixes a/ or b/
142 if s.match (/^(a|b)\//) then s.substr(2) else s
143
144ltrim = (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
150makeString = (s) -> if s == null then '' else s + ''
151
152trimLeft = String.prototype.trimLeft
153
154defaultToWhiteSpace = (chars) ->
155 return '\\s' if chars == null
156 return chars.source if chars.source
157 return '[' + escapeRegExp(chars) + ']'
158
159escapeRegExp = (s) ->
160 makeString(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1')