UNPKG

3.58 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 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 # todo beter regexp to avoid detect normal line starting with diff
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
127parseFile = (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# fallback function to overwrite file.from and file.to if executed
137parseFileFallback = (s) ->
138 s = ltrim s, '-'
139 s = ltrim s, '+'
140 s = s.trim()
141 # ignore possible time stamp
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 # ignore git prefixes a/ or b/
145 if s.match (/^(a|b)\//) then s.substr(2) else s
146
147ltrim = (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
153makeString = (s) -> if s == null then '' else s + ''
154
155trimLeft = String.prototype.trimLeft
156
157defaultToWhiteSpace = (chars) ->
158 return '\\s' if chars == null
159 return chars.source if chars.source
160 return '[' + escapeRegExp(chars) + ']'
161
162escapeRegExp = (s) ->
163 makeString(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1')