95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Count .dp literals by whether they sit in a spacing position or a dimension one.
|
||
|
|
|
||
|
|
m3-audit.sh used to split them by value, exempting a list of numbers that "looked like"
|
||
|
|
dimensions -- which is wrong twice over. 16.dp is a spacing stop *and* a plausible icon
|
||
|
|
size, and 50.dp was a Spacer height in 53 places and a divider width in one. Only the call
|
||
|
|
shape says which, so this reads the shape.
|
||
|
|
|
||
|
|
spacing padding(...), PaddingValues(...), Arrangement.spacedBy(...), and a
|
||
|
|
.height()/.width() whose enclosing call is Spacer(
|
||
|
|
dimension .size(), a non-Spacer .height()/.width(), RoundedCornerShape(),
|
||
|
|
BorderStroke(), a `size =` argument
|
||
|
|
|
||
|
|
Spacing positions are what phase 2 drives to zero; dimensions are out of its scope and are
|
||
|
|
reported so the number is visible rather than exempted.
|
||
|
|
|
||
|
|
Usage: m3-spacing-positions.py [--list]
|
||
|
|
"""
|
||
|
|
import os, re, sys
|
||
|
|
|
||
|
|
UI = 'composeApp/src/commonMain/kotlin/press/mantra/compose/ui'
|
||
|
|
THEME = os.path.join(UI, 'theme')
|
||
|
|
|
||
|
|
|
||
|
|
def _spans(text, opener):
|
||
|
|
"""Brace-matched character ranges of every call matching `opener`."""
|
||
|
|
spans = []
|
||
|
|
for m in re.finditer(opener, text):
|
||
|
|
depth, i = 0, m.end() - 1
|
||
|
|
while i < len(text):
|
||
|
|
if text[i] == '(':
|
||
|
|
depth += 1
|
||
|
|
elif text[i] == ')':
|
||
|
|
depth -= 1
|
||
|
|
if depth == 0:
|
||
|
|
spans.append((m.start(), i))
|
||
|
|
break
|
||
|
|
i += 1
|
||
|
|
return spans
|
||
|
|
|
||
|
|
|
||
|
|
def classify(path):
|
||
|
|
text = open(path, encoding='utf-8').read()
|
||
|
|
pads = _spans(text, r'\.?\bpadding\s*\(|\bPaddingValues\s*\(')
|
||
|
|
gaps = _spans(text, r'\bspacedBy\s*\(')
|
||
|
|
spacers = _spans(text, r'\bSpacer\s*\(')
|
||
|
|
|
||
|
|
def inside(pos, spans):
|
||
|
|
return any(a <= pos <= b for a, b in spans)
|
||
|
|
|
||
|
|
hw = [
|
||
|
|
(m.start(1), m.end(1))
|
||
|
|
for m in re.finditer(r'\.(?:height|width)\s*\(\s*(\d+\.dp)\s*\)', text)
|
||
|
|
if inside(m.start(), spacers)
|
||
|
|
]
|
||
|
|
|
||
|
|
spacing, dimension = [], []
|
||
|
|
for m in re.finditer(r'\b(\d+)\.dp\b', text):
|
||
|
|
pos = m.start()
|
||
|
|
line_start = text.rfind('\n', 0, pos) + 1
|
||
|
|
line = text[line_start:text.find('\n', pos)]
|
||
|
|
if re.match(r'\s*(//|\*|/\*)', line):
|
||
|
|
continue
|
||
|
|
entry = (path, text[:pos].count('\n') + 1, m.group(0), line.strip()[:90])
|
||
|
|
if inside(pos, pads) or inside(pos, gaps) or any(a <= pos < b for a, b in hw):
|
||
|
|
spacing.append(entry)
|
||
|
|
else:
|
||
|
|
dimension.append(entry)
|
||
|
|
return spacing, dimension
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
spacing, dimension = [], []
|
||
|
|
for root, _, files in os.walk(UI):
|
||
|
|
if root.startswith(THEME):
|
||
|
|
continue
|
||
|
|
for f in sorted(files):
|
||
|
|
if f.endswith('.kt'):
|
||
|
|
s, d = classify(os.path.join(root, f))
|
||
|
|
spacing += s
|
||
|
|
dimension += d
|
||
|
|
|
||
|
|
print(f' {"dp literals in spacing positions":42s} {len(spacing):6d}')
|
||
|
|
print(f' {"dp literals in dimension positions":42s} {len(dimension):6d} (out of scope)')
|
||
|
|
|
||
|
|
if '--list' in sys.argv:
|
||
|
|
for path, line, lit, ctx in spacing:
|
||
|
|
print(f' {path.replace(UI + "/", "")}:{line} {lit:>7s} {ctx}')
|
||
|
|
|
||
|
|
return 1 if spacing else 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
sys.exit(main())
|