-
Notifications
You must be signed in to change notification settings - Fork 2
/
smap.py
executable file
·123 lines (95 loc) · 3.38 KB
/
smap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/python
"""A module for parsing source maps, as output by the Closure and
CoffeeScript compilers and consumed by browsers. See
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/
"""
import collections
import json
import sys
SmapState = collections.namedtuple(
'SmapState', ['dst_line', 'dst_col',
'src', 'src_line', 'src_col',
'name'])
# Mapping of base64 letter -> integer value.
B64 = dict((c, i) for i, c in
enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
'0123456789+/'))
def parse_vlq(segment):
"""Parse a string of VLQ-encoded data.
Returns:
a list of integers.
"""
values = []
cur, shift = 0, 0
for c in segment:
val = B64[c]
# Each character is 6 bits:
# 5 of value and the high bit is the continuation.
val, cont = val & 0b11111, val >> 5
cur += val << shift
shift += 5
if not cont:
# The low bit of the unpacked value is the sign.
cur, sign = cur >> 1, cur & 1
if sign:
cur = -cur
values.append(cur)
cur, shift = 0, 0
if cur or shift:
raise Exception('leftover cur/shift in vlq decode')
return values
def parse_vlq_test():
assert parse_vlq('gqjG') == [100000]
assert parse_vlq('hqjG') == [-100000]
assert parse_vlq('DFLx+BhqjG') == [-1, -2, -5, -1000, -100000]
assert parse_vlq('CEKw+BgqjG') == [1, 2, 5, 1000, 100000]
assert parse_vlq('/+Z') == [-13295]
def parse_smap(f):
"""Given a file-like object, yield SmapState()s as they are read from it."""
smap = json.load(f)
sources = smap['sources']
names = smap['names']
mappings = smap['mappings']
lines = mappings.split(';')
dst_col, src_id, src_line, src_col, name_id = 0, 0, 0, 0, 0
for dst_line, line in enumerate(lines):
segments = line.split(',')
dst_col = 0
for segment in segments:
if not segment:
continue
parse = parse_vlq(segment)
dst_col += parse[0]
src = None
name = None
if len(parse) > 1:
src_id += parse[1]
src = sources[src_id]
src_line += parse[2]
src_col += parse[3]
if len(parse) > 4:
name_id += parse[4]
name = names[name_id]
assert dst_line >= 0
assert dst_col >= 0
assert src_line >= 0
assert src_col >= 0
yield SmapState(dst_line, dst_col, src, src_line, src_col, name)
def demo():
# Simple demo that shows files that most contribute to total size.
cost = collections.Counter()
last_state = None
for state in parse_smap(open(sys.argv[1])):
if last_state:
# Note: not sure this is correct -- reread the spec to be sure.
src = state.src or last_state.src
if state.dst_line == last_state.dst_line:
span = state.dst_col - last_state.dst_col
cost[src] += span
# print 'out[%d:%d] = %s[%d:%d] %s' % (
# state.dst_line+1, state.dst_col,
# state.src, state.src_line+1, state.src_col, state.name)
last_state = state
for file, bytes in cost.most_common():
print bytes, file
demo()