mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
252 lines
6.4 KiB
Python
Executable File
252 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
|
|
from collections import namedtuple
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
|
|
Bundle = namedtuple('Bundle' , 'bundleName totalBytes files')
|
|
|
|
Diff = namedtuple('Diff', 'status path oldBytes newBytes isBigger diff')
|
|
BundleDiff = namedtuple('BundleDiff', 'total files')
|
|
|
|
|
|
class Status:
|
|
added = 'added'
|
|
modified = 'modified'
|
|
deleted = 'deleted'
|
|
|
|
|
|
def humanize(nbytes):
|
|
suffixes = ['B', 'KB', 'MB']
|
|
i = 0
|
|
while abs(nbytes) >= 1024 and i < len(suffixes)-1:
|
|
nbytes /= 1024.
|
|
i += 1
|
|
f = ('%.1f' % nbytes).rstrip('0').rstrip('.')
|
|
return '%s %s' % (f, suffixes[i])
|
|
|
|
|
|
def normalizePath(path):
|
|
if path.startswith('node_modules/') or '/node_modules/' in path:
|
|
mod = path.partition('/node_modules/')[2].split('/')
|
|
if mod[0].startswith('@'):
|
|
return '/'.join(mod[0:2])
|
|
return mod[0]
|
|
return path
|
|
|
|
|
|
def normalizeBundleName(path):
|
|
return re.sub(r'\-.{8}\.([a-zA-Z-])', r'.\1', path)
|
|
|
|
|
|
def mergeFiles(allFiles):
|
|
files = {}
|
|
for path, meta in allFiles.items():
|
|
np = normalizePath(path)
|
|
p = os.path.dirname(np) or np
|
|
if p not in files:
|
|
files[p] = 0
|
|
files[p] += meta['size']
|
|
return files
|
|
|
|
|
|
def mergeSize(a, b):
|
|
for k,v in b.items():
|
|
if k in a:
|
|
a[k] += v
|
|
else:
|
|
a[k] = v
|
|
return a
|
|
|
|
|
|
def mergeBundles(allBundles):
|
|
bundles = {}
|
|
for a in allBundles:
|
|
if a.bundleName not in bundles:
|
|
bundles[a.bundleName] = Bundle(
|
|
bundleName=a.bundleName,
|
|
totalBytes=0,
|
|
files={},
|
|
)
|
|
bundles[a.bundleName] = Bundle(
|
|
bundleName=a.bundleName,
|
|
totalBytes=bundles[a.bundleName].totalBytes + a.totalBytes,
|
|
files=mergeSize(bundles[a.bundleName].files, a.files),
|
|
)
|
|
return bundles.values()
|
|
|
|
|
|
def readBundle(path):
|
|
bundles = []
|
|
with open(path) as f:
|
|
data = json.load(f)
|
|
for result in data['results']:
|
|
bundle = Bundle(
|
|
bundleName=normalizeBundleName(result['bundleName']),
|
|
totalBytes=result['totalBytes'],
|
|
files=mergeFiles(result['files']))
|
|
bundles.append(bundle)
|
|
return mergeBundles(bundles)
|
|
|
|
|
|
def printRow(diff, element):
|
|
if diff.status == Status.added:
|
|
status = '+'
|
|
elif diff.status == Status.modified:
|
|
status = 'M'
|
|
else:
|
|
status = '-'
|
|
|
|
sign = '+' if diff.isBigger else ''
|
|
ob = humanize(diff.oldBytes) if diff.oldBytes > 0 else ''
|
|
nb = humanize(diff.newBytes) if diff.newBytes > 0 else ''
|
|
|
|
return '''<tr>
|
|
<{element}>{status}</{element}>
|
|
<{element}>{path}</{element}>
|
|
<{element}>{ob}</{element}>
|
|
<{element}>{nb}</{element}>
|
|
<{element}>{sign}{diff}</{element}>
|
|
</tr>'''.format(
|
|
element=element,
|
|
status=status, path=diff.path,
|
|
ob=ob, nb=nb,
|
|
sign=sign, diff=humanize(diff.diff))
|
|
|
|
|
|
def printBundleTable(diffs, element):
|
|
rows = []
|
|
for diff in diffs:
|
|
rows.append(printRow(diff, element))
|
|
return '<table><tbody>{body}</tbody></table>'.format(body='\n'.join(rows))
|
|
|
|
|
|
def printBundleDiff(bundleDiff):
|
|
summary = printBundleTable([bundleDiff.total], 'th')
|
|
details = printBundleTable(bundleDiff.files, 'td')
|
|
|
|
return '''
|
|
<details>
|
|
<summary>{summary}</summary>
|
|
{details}
|
|
</details>
|
|
'''.format(summary=summary, details=details)
|
|
|
|
|
|
def makeDiff(path, oldBytes, newBytes):
|
|
if oldBytes > 0 and newBytes > 0:
|
|
status = Status.modified
|
|
elif oldBytes == 0:
|
|
status = Status.added
|
|
else:
|
|
status = Status.deleted
|
|
return Diff(
|
|
status=status,
|
|
path=path,
|
|
oldBytes=oldBytes,
|
|
newBytes=newBytes,
|
|
isBigger=newBytes > oldBytes,
|
|
diff=newBytes - oldBytes,
|
|
)
|
|
|
|
|
|
def diffBundle(ba, bb):
|
|
filesDiffs = []
|
|
diff = bb.totalBytes - ba.totalBytes
|
|
if abs(diff) > 500:
|
|
totalDiff = makeDiff(ba.bundleName, ba.totalBytes, bb.totalBytes)
|
|
|
|
for aFile in ba.files:
|
|
aSize = ba.files[aFile]
|
|
if aFile in bb.files:
|
|
bSize = bb.files[aFile]
|
|
if aSize != bSize:
|
|
filesDiffs.append(makeDiff(aFile, aSize, bSize))
|
|
else:
|
|
filesDiffs.append(makeDiff(aFile, aSize, 0))
|
|
|
|
for bFile in bb.files:
|
|
bSize = bb.files[bFile]
|
|
if bFile not in ba.files:
|
|
filesDiffs.append(makeDiff(bFile, 0, bSize))
|
|
|
|
return BundleDiff(total=totalDiff, files=filesDiffs)
|
|
|
|
|
|
def summarize(diffs):
|
|
totalDiff = 0
|
|
for diff in diffs:
|
|
totalDiff += diff.total.diff
|
|
sign = '+' if totalDiff > 0 else ''
|
|
return '### Total diff: {sign}{totalDiff}'.format(
|
|
sign=sign, totalDiff=humanize(totalDiff))
|
|
|
|
|
|
def printDiff(diffs):
|
|
rows = []
|
|
for diff in diffs:
|
|
rows.append(printBundleDiff(diff))
|
|
summary = summarize(diffs)
|
|
print('{summary}\n{rows}'.format(summary=summary, rows='\n'.join(rows)))
|
|
|
|
|
|
def diffBundles(a, b):
|
|
diffs = []
|
|
for ba in a:
|
|
found = False
|
|
for bb in b:
|
|
if ba.bundleName == bb.bundleName:
|
|
found = True
|
|
d = diffBundle(ba, bb)
|
|
if d:
|
|
diffs.append(d)
|
|
break
|
|
if not found:
|
|
bb = Bundle(
|
|
bundleName=ba.bundleName,
|
|
totalBytes=0,
|
|
files={}
|
|
)
|
|
d = diffBundle(ba, bb)
|
|
if d:
|
|
diffs.append(d)
|
|
|
|
for bb in b:
|
|
found = False
|
|
for ba in a:
|
|
if ba.bundleName == bb.bundleName:
|
|
found = True
|
|
if not found:
|
|
ba = Bundle(
|
|
bundleName=bb.bundleName,
|
|
totalBytes=0,
|
|
files={}
|
|
)
|
|
d = diffBundle(ba, bb)
|
|
if d:
|
|
diffs.append(d)
|
|
|
|
diffs.sort(key=lambda x: x.total.newBytes, reverse=True)
|
|
return diffs
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) != 3:
|
|
print('Usage: PATH1 PATH2')
|
|
sys.exit(1)
|
|
|
|
bundleA = readBundle(sys.argv[1])
|
|
bundleB = readBundle(sys.argv[2])
|
|
|
|
if not bundleA or not bundleB:
|
|
print('Usage: PATH1 PATH2')
|
|
sys.exit(1)
|
|
|
|
diffs = diffBundles(bundleA, bundleB)
|
|
if diffs:
|
|
printDiff(diffs)
|