scripts: Added -U/--undefine as inverse -D/--define to most scripts

This adds -U/--undefine as an inverse -D/--define, allowing you to
select results where a given field does _not_ match a set of
values/globs.

For example, make bench-marks, which need to ignore stack/heap/usage
probes as a special case, can easily filter like so:

  $ ./scripts/csv.py test.csv -Uprobe=stack,heap,usage

---

One thing globbing is pretty bad at is inverse matches. This is
_usually_ easy enough to work around, but has been an annoyance enough
times that I think _some_ option to inverse filter is warranted.

I'm not sure -U/--undefine is the best name for this, since field isn't
really "undefined" as a result (well kinda? if you're relying on
implicit by/field rules?), but it gets the job done.
This commit is contained in:
Christopher Haster
2026-02-07 00:16:00 -06:00
parent 37288fceab
commit ba1f5e730d
14 changed files with 438 additions and 101 deletions
+6 -18
View File
@@ -600,9 +600,7 @@ bench-marks: SUMMARYFLAGS+=-Si
bench-marks: $(BENCH_CSV) bench-marks: $(BENCH_CSV)
$(strip ./scripts/csv.py \ $(strip ./scripts/csv.py \
<(./scripts/csv.py $^ \ <(./scripts/csv.py $^ \
-Dprobe=append,remove,create,delete,fetch,lookup,$\ -Uprobe=stack,heap,usage \
commit,namelookup,$\
write,stat,read \
-bprobe='%(case)s+%(probe)s' \ -bprobe='%(case)s+%(probe)s' \
-Fi='min(enumerate())' \ -Fi='min(enumerate())' \
-fn='max(n)' \ -fn='max(n)' \
@@ -624,18 +622,14 @@ bench-marks-csv: $(BUILDDIR)/lfs3.bench.csv
bench-marks-diff: $(BENCH_CSV) bench-marks-diff: $(BENCH_CSV)
$(strip ./scripts/csv.py \ $(strip ./scripts/csv.py \
<(./scripts/csv.py $^ \ <(./scripts/csv.py $^ \
-Dprobe=append,remove,create,delete,fetch,lookup,$\ -Uprobe=stack,heap,usage \
commit,namelookup,$\
write,stat,read \
-bprobe='%(case)s+%(probe)s' \ -bprobe='%(case)s+%(probe)s' \
-Fi='min(enumerate())' \ -Fi='min(enumerate())' \
-fn='max(n)' \ -fn='max(n)' \
-ft='max(float(bench_simtime)/1.0e9)' \ -ft='max(float(bench_simtime)/1.0e9)' \
-o-) \ -o-) \
-d <(./scripts/csv.py $(BUILDDIR)/lfs3.bench.csv \ -d <(./scripts/csv.py $(BUILDDIR)/lfs3.bench.csv \
-Dprobe=append,remove,create,delete,fetch,lookup,$\ -Uprobe=stack,heap,usage \
commit,namelookup,$\
write,stat,read \
-bprobe='%(case)s+%(probe)s' \ -bprobe='%(case)s+%(probe)s' \
-Fi='min(enumerate())' \ -Fi='min(enumerate())' \
-fn='max(n)' \ -fn='max(n)' \
@@ -652,9 +646,7 @@ bench-bottlenecks: SUMMARYFLAGS+=-Sruntime
bench-bottlenecks: $(BENCH_CSV) bench-bottlenecks: $(BENCH_CSV)
$(strip ./scripts/csv.py \ $(strip ./scripts/csv.py \
<(./scripts/csv.py $^ \ <(./scripts/csv.py $^ \
-Dprobe=append,remove,create,delete,fetch,lookup,$\ -Uprobe=stack,heap,usage \
commit,namelookup,$\
write,stat,read \
-bprobe='%(case)s+%(probe)s' \ -bprobe='%(case)s+%(probe)s' \
-Fi='min(enumerate())' \ -Fi='min(enumerate())' \
-fn='max(n)' \ -fn='max(n)' \
@@ -673,9 +665,7 @@ bench-bottlenecks: $(BENCH_CSV)
bench-ops: SUMMARYFLAGS+=-Si bench-ops: SUMMARYFLAGS+=-Si
bench-ops: $(BENCH_CSV) bench-ops: $(BENCH_CSV)
$(strip ./scripts/csv.py $^ \ $(strip ./scripts/csv.py $^ \
-Dprobe=append,remove,create,delete,fetch,lookup,$\ -Uprobe=stack,heap,usage \
commit,namelookup,$\
write,stat,read \
-bprobe='%(case)s+%(probe)s' \ -bprobe='%(case)s+%(probe)s' \
-Hprobe=bench+probe \ -Hprobe=bench+probe \
-Fi='min(enumerate())' \ -Fi='min(enumerate())' \
@@ -692,9 +682,7 @@ bench-ops: $(BENCH_CSV)
bench-widths: SUMMARYFLAGS+=-Si bench-widths: SUMMARYFLAGS+=-Si
bench-widths: $(BENCH_CSV) bench-widths: $(BENCH_CSV)
$(strip ./scripts/csv.py $^ \ $(strip ./scripts/csv.py $^ \
-Dprobe=append,remove,create,delete,fetch,lookup,$\ -Uprobe=stack,heap,usage \
commit,namelookup,$\
write,stat,read \
-bprobe='%(case)s+%(probe)s' \ -bprobe='%(case)s+%(probe)s' \
-Hprobe=bench+probe \ -Hprobe=bench+probe \
-Fi='min(enumerate())' \ -Fi='min(enumerate())' \
+27 -5
View File
@@ -529,6 +529,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -540,20 +541,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -1048,6 +1056,7 @@ def main(obj_paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
**args): **args):
# figure out what fields we're interested in # figure out what fields we're interested in
@@ -1080,6 +1089,7 @@ def main(obj_paths, *,
results = fold(CodeResult, results, results = fold(CodeResult, results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
sort=sort) sort=sort)
# find previous results? # find previous results?
@@ -1096,7 +1106,8 @@ def main(obj_paths, *,
# fold # fold
diff_results = fold(CodeResult, diff_results, diff_results = fold(CodeResult, diff_results,
by=by, by=by,
defines=defines) defines=defines,
undefines=undefines)
# write results to JSON # write results to JSON
if args.get('output_json'): if args.get('output_json'):
@@ -1188,6 +1199,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+27 -5
View File
@@ -398,6 +398,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -409,20 +410,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -992,6 +1000,7 @@ def main(gcda_paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
hits=False, hits=False,
**args): **args):
@@ -1046,6 +1055,7 @@ def main(gcda_paths, *,
results = fold(CovResult, results, results = fold(CovResult, results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
sort=sort) sort=sort)
# find previous results? # find previous results?
@@ -1062,7 +1072,8 @@ def main(gcda_paths, *,
# fold # fold
diff_results = fold(CovResult, diff_results, diff_results = fold(CovResult, diff_results,
by=by, by=by,
defines=defines) defines=defines,
undefines=undefines)
# annotate sources # annotate sources
if (args.get('annotate') if (args.get('annotate')
@@ -1167,6 +1178,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+33 -4
View File
@@ -2069,6 +2069,7 @@ def compile(fields_, results,
def homogenize(Result, results, *, def homogenize(Result, results, *,
defines=[], defines=[],
undefines=[],
depth=1, depth=1,
depth_=0, depth_=0,
**_): **_):
@@ -2087,6 +2088,10 @@ def homogenize(Result, results, *,
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
continue continue
if any(any(fnmatch.fnmatchcase(str(r.get(k, '')), v)
for v in vs)
for k, vs in undefines):
continue
# append a result # append a result
results_.append(Result( results_.append(Result(
@@ -2125,6 +2130,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -2136,20 +2142,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -2920,6 +2933,7 @@ def main(csv_paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=None, depth=None,
children=None, children=None,
@@ -3039,6 +3053,7 @@ def main(csv_paths, *,
if not any(k == k_ for (k_, _), _ in (by or [])) if not any(k == k_ for (k_, _), _ in (by or []))
and not any(k == k_ for (k_, _), _ in (fields or [])) and not any(k == k_ for (k_, _), _ in (fields or []))
and not any(k == k_ for k_, _ in defines) and not any(k == k_ for k_, _ in defines)
and not any(k == k_ for k_, _ in undefines)
and not any(k == k_ for (k_, _), _ in (sort or [])) and not any(k == k_ for (k_, _), _ in (sort or []))
and not any(k == k_ for (k_, _), _ in (hot or [])) and not any(k == k_ for (k_, _), _ in (hot or []))
and k != z and k != z
@@ -3054,6 +3069,7 @@ def main(csv_paths, *,
if not any(k == k_ for (k_, _), _ in (by or [])) if not any(k == k_ for (k_, _), _ in (by or []))
and not any(k == k_ for (k_, _), _ in (fields or [])) and not any(k == k_ for (k_, _), _ in (fields or []))
and not any(k == k_ for k_, _ in defines) and not any(k == k_ for k_, _ in defines)
and not any(k == k_ for k_, _ in undefines)
and not any(k == k_ for (k_, _), _ in (sort or [])) and not any(k == k_ for (k_, _), _ in (sort or []))
and not any(k == k_ for (k_, _), _ in (hot or [])) and not any(k == k_ for (k_, _), _ in (hot or []))
and k != z and k != z
@@ -3093,6 +3109,7 @@ def main(csv_paths, *,
# homogenize # homogenize
results = homogenize(Result, results, results = homogenize(Result, results,
defines=defines, defines=defines,
undefines=undefines,
depth=depth) depth=depth)
# fold # fold
@@ -3128,6 +3145,7 @@ def main(csv_paths, *,
# homogenize # homogenize
diff_results = homogenize(Result, diff_results, diff_results = homogenize(Result, diff_results,
defines=defines, defines=defines,
undefines=undefines,
depth=depth) depth=depth)
# fold # fold
@@ -3319,6 +3337,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+26 -4
View File
@@ -745,6 +745,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -756,20 +757,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -1307,6 +1315,7 @@ def main(obj_paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=None, depth=None,
hot=None, hot=None,
@@ -1356,6 +1365,7 @@ def main(obj_paths, *,
results = fold(CtxResult, results, results = fold(CtxResult, results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
sort=sort, sort=sort,
depth=depth) depth=depth)
@@ -1381,6 +1391,7 @@ def main(obj_paths, *,
diff_results = fold(CtxResult, diff_results, diff_results = fold(CtxResult, diff_results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
depth=depth) depth=depth)
# hotify? # hotify?
@@ -1483,6 +1494,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+27 -5
View File
@@ -529,6 +529,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -540,20 +541,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -1048,6 +1056,7 @@ def main(obj_paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
**args): **args):
# figure out what fields we're interested in # figure out what fields we're interested in
@@ -1080,6 +1089,7 @@ def main(obj_paths, *,
results = fold(DataResult, results, results = fold(DataResult, results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
sort=sort) sort=sort)
# find previous results? # find previous results?
@@ -1096,7 +1106,8 @@ def main(obj_paths, *,
# fold # fold
diff_results = fold(DataResult, diff_results, diff_results = fold(DataResult, diff_results,
by=by, by=by,
defines=defines) defines=defines,
undefines=undefines)
# write results to JSON # write results to JSON
if args.get('output_json'): if args.get('output_json'):
@@ -1188,6 +1199,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+26 -4
View File
@@ -844,6 +844,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -855,20 +856,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -1505,6 +1513,7 @@ def main_(perf_paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
branches=False, branches=False,
caches=False, caches=False,
@@ -1574,6 +1583,7 @@ def main_(perf_paths, *,
results = fold(PerfResult, results, results = fold(PerfResult, results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
sort=sort, sort=sort,
depth=depth) depth=depth)
@@ -1599,6 +1609,7 @@ def main_(perf_paths, *,
diff_results = fold(PerfResult, diff_results, diff_results = fold(PerfResult, diff_results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
depth=depth) depth=depth)
# hotify? # hotify?
@@ -1727,6 +1738,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+26 -4
View File
@@ -818,6 +818,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -829,20 +830,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -1493,6 +1501,7 @@ def main_(paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=None, depth=None,
hot=None, hot=None,
@@ -1570,6 +1579,7 @@ def main_(paths, *,
results = fold(PerfBdResult, results, results = fold(PerfBdResult, results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
sort=sort, sort=sort,
depth=depth) depth=depth)
@@ -1595,6 +1605,7 @@ def main_(paths, *,
diff_results = fold(PerfBdResult, diff_results, diff_results = fold(PerfBdResult, diff_results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
depth=depth) depth=depth)
# hotify? # hotify?
@@ -1728,6 +1739,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+53 -13
View File
@@ -356,7 +356,9 @@ class Rev(co.namedtuple('Rev', 'a')):
def __ge__(self, other): def __ge__(self, other):
return self.a <= other.a return self.a <= other.a
def collect(csv_paths, defines=[]): def collect(csv_paths, *,
defines=[],
undefines=[]):
# collect results from CSV files # collect results from CSV files
fields = [] fields = []
results = [] results = []
@@ -373,22 +375,34 @@ def collect(csv_paths, defines=[]):
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
continue continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results.append(r) results.append(r)
except FileNotFoundError: except FileNotFoundError:
pass pass
return fields, results return fields, results
def fold(results, by=None, x=None, y=None, defines=[]): def fold(results, by=None, x=None, y=None, *,
defines=[],
undefines=[]):
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(r.get(k, ''), v) if not all(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
if by: if by:
@@ -1232,7 +1246,8 @@ def main_(ring, csv_paths, *,
by=None, by=None,
x=None, x=None,
y=None, y=None,
define=[], defines=[],
undefines=[],
sort=None, sort=None,
labels=[], labels=[],
chars=[], chars=[],
@@ -1330,10 +1345,17 @@ def main_(ring, csv_paths, *,
all_x = (x or []) + subplots_get('x', **subplot, subplots=subplots) all_x = (x or []) + subplots_get('x', **subplot, subplots=subplots)
all_y = (y or []) + subplots_get('y', **subplot, subplots=subplots) all_y = (y or []) + subplots_get('y', **subplot, subplots=subplots)
all_defines = co.defaultdict(lambda: set()) all_defines = co.defaultdict(lambda: set())
for k, vs in it.chain(define or [], for k, vs in it.chain(
subplots_get('define', **subplot, subplots=subplots)): defines,
subplots_get('defines', **subplot, subplots=subplots)):
all_defines[k] |= vs all_defines[k] |= vs
all_defines = sorted(all_defines.items()) all_defines = sorted(all_defines.items())
all_undefines = co.defaultdict(lambda: set())
for k, vs in it.chain(
undefines,
subplots_get('undefines', **subplot, subplots=subplots)):
all_undefines[k] |= vs
all_undefines = sorted(all_undefines.items())
if not all_by and not all_y: if not all_by and not all_y:
print("error: needs --by or -y to figure out fields", print("error: needs --by or -y to figure out fields",
@@ -1423,14 +1445,17 @@ def main_(ring, csv_paths, *,
## our main drawing logic ## our main drawing logic
# first collect results from CSV files # first collect results from CSV files
fields_, results = collect(csv_paths) fields_, results = collect(csv_paths,
defines=defines,
undefines=undefines)
# if y not specified, guess it's anything not in by/defines/x # if y not specified, guess it's anything not in by/defines/x
all_y_ = all_y all_y_ = all_y
if not all_y: if not all_y:
all_y_ = [k for k in fields_ all_y_ = [k for k in fields_
if k not in all_by if k not in all_by
and not any(k == k_ for k_, _ in all_defines)] and not any(k == k_ for k_, _ in all_defines)
and not any(k == k_ for k_, _ in all_undefines)]
# then extract the requested datasets # then extract the requested datasets
# #
@@ -1603,7 +1628,8 @@ def main_(ring, csv_paths, *,
# allow subplot params to override global params # allow subplot params to override global params
x_ = set((x or []) + s.args.get('x', [])) x_ = set((x or []) + s.args.get('x', []))
y_ = set((y or []) + s.args.get('y', [])) y_ = set((y or []) + s.args.get('y', []))
define_ = define + s.args.get('define', []) defines_ = defines + s.args.get('defines', [])
undefines_ = undefines + s.args.get('undefines', [])
xlim_ = s.args.get('xlim', xlim) xlim_ = s.args.get('xlim', xlim)
ylim_ = s.args.get('ylim', ylim) ylim_ = s.args.get('ylim', ylim)
xlim_stddev_ = s.args.get('xlim_stddev', xlim_stddev) xlim_stddev_ = s.args.get('xlim_stddev', xlim_stddev)
@@ -1630,7 +1656,9 @@ def main_(ring, csv_paths, *,
# data can be constrained by subplot-specific defines, # data can be constrained by subplot-specific defines,
# so re-extract for each plot # so re-extract for each plot
subdatasets, subdataattrs = fold( subdatasets, subdataattrs = fold(
results, all_by, all_x, all_y_, define_) results, all_by, all_x, all_y_,
defines=defines_,
undefines=undefines_)
# order by labels # order by labels
subdatasets = co.OrderedDict(sorted( subdatasets = co.OrderedDict(sorted(
@@ -1991,14 +2019,26 @@ if __name__ == "__main__":
help="Field to use for the y-axis.") help="Field to use for the y-axis.")
parser.add_argument( parser.add_argument(
'-D', '--define', '-D', '--define',
dest='defines',
action='append',
type=lambda x: ( type=lambda x: (
lambda k, vs: ( lambda k, vs: (
k.strip(), k.strip(),
{v.strip() for v in vs.split(',')}) {v.strip() for v in vs.split(',')})
)(*x.split('=', 1)), )(*x.split('=', 1)),
action='append',
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+53 -13
View File
@@ -240,7 +240,9 @@ class Rev(co.namedtuple('Rev', 'a')):
def __ge__(self, other): def __ge__(self, other):
return self.a <= other.a return self.a <= other.a
def collect(csv_paths, defines=[]): def collect(csv_paths, *,
defines=[],
undefines=[]):
# collect results from CSV files # collect results from CSV files
fields = [] fields = []
results = [] results = []
@@ -257,22 +259,34 @@ def collect(csv_paths, defines=[]):
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
continue continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results.append(r) results.append(r)
except FileNotFoundError: except FileNotFoundError:
pass pass
return fields, results return fields, results
def fold(results, by=None, x=None, y=None, defines=[]): def fold(results, by=None, x=None, y=None, *,
defines=[],
undefines=[]):
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(r.get(k, ''), v) if not all(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
if by: if by:
@@ -826,7 +840,8 @@ def main(csv_paths, output, *,
by=None, by=None,
x=None, x=None,
y=None, y=None,
define=[], defines=[],
undefines=[],
sort=None, sort=None,
labels=[], labels=[],
colors=[], colors=[],
@@ -978,10 +993,17 @@ def main(csv_paths, output, *,
all_x = (x or []) + subplots_get('x', **subplot, subplots=subplots) all_x = (x or []) + subplots_get('x', **subplot, subplots=subplots)
all_y = (y or []) + subplots_get('y', **subplot, subplots=subplots) all_y = (y or []) + subplots_get('y', **subplot, subplots=subplots)
all_defines = co.defaultdict(lambda: set()) all_defines = co.defaultdict(lambda: set())
for k, vs in it.chain(define or [], for k, vs in it.chain(
subplots_get('define', **subplot, subplots=subplots)): defines,
subplots_get('defines', **subplot, subplots=subplots)):
all_defines[k] |= vs all_defines[k] |= vs
all_defines = sorted(all_defines.items()) all_defines = sorted(all_defines.items())
all_undefines = co.defaultdict(lambda: set())
for k, vs in it.chain(
undefines,
subplots_get('undefines', **subplot, subplots=subplots)):
all_undefines[k] |= vs
all_undefines = sorted(all_undefines.items())
if not all_by and not all_y: if not all_by and not all_y:
print("error: needs --by or -y to figure out fields", print("error: needs --by or -y to figure out fields",
@@ -989,13 +1011,16 @@ def main(csv_paths, output, *,
sys.exit(-1) sys.exit(-1)
# first collect results from CSV files # first collect results from CSV files
fields_, results = collect(csv_paths) fields_, results = collect(csv_paths,
defines=defines,
undefines=undefines)
# if y not specified, guess it's anything not in by/defines/x # if y not specified, guess it's anything not in by/defines/x
if not all_y: if not all_y:
all_y = [k for k in fields_ all_y = [k for k in fields_
if k not in all_by if k not in all_by
and not any(k == k_ for k_, _ in all_defines)] and not any(k == k_ for k_, _ in all_defines)
and not any(k == k_ for k_, _ in all_undefines)]
# then extract the requested datasets # then extract the requested datasets
# #
@@ -1065,7 +1090,8 @@ def main(csv_paths, output, *,
# allow subplot params to override global params # allow subplot params to override global params
x_ = set((x or []) + s.args.get('x', [])) x_ = set((x or []) + s.args.get('x', []))
y_ = set((y or []) + s.args.get('y', [])) y_ = set((y or []) + s.args.get('y', []))
define_ = define + s.args.get('define', []) defines_ = defines + s.args.get('defines', [])
undefines_ = undefines + s.args.get('undefines', [])
xlim_ = s.args.get('xlim', xlim) xlim_ = s.args.get('xlim', xlim)
ylim_ = s.args.get('ylim', ylim) ylim_ = s.args.get('ylim', ylim)
xlim_stddev_ = s.args.get('xlim_stddev', xlim_stddev) xlim_stddev_ = s.args.get('xlim_stddev', xlim_stddev)
@@ -1105,7 +1131,9 @@ def main(csv_paths, output, *,
# data can be constrained by subplot-specific defines, # data can be constrained by subplot-specific defines,
# so re-extract for each plot # so re-extract for each plot
subdatasets, subdataattrs = fold( subdatasets, subdataattrs = fold(
results, all_by, all_x, all_y, define_) results, all_by, all_x, all_y,
defines=defines_,
undefines=undefines_)
# order by labels # order by labels
subdatasets = co.OrderedDict(sorted( subdatasets = co.OrderedDict(sorted(
@@ -1447,14 +1475,26 @@ if __name__ == "__main__":
help="Field to use for the y-axis.") help="Field to use for the y-axis.")
parser.add_argument( parser.add_argument(
'-D', '--define', '-D', '--define',
dest='defines',
action='append',
type=lambda x: ( type=lambda x: (
lambda k, vs: ( lambda k, vs: (
k.strip(), k.strip(),
{v.strip() for v in vs.split(',')}) {v.strip() for v in vs.split(',')})
)(*x.split('=', 1)), )(*x.split('=', 1)),
action='append',
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+26 -4
View File
@@ -486,6 +486,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -497,20 +498,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -1048,6 +1056,7 @@ def main(ci_paths,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=None, depth=None,
hot=None, hot=None,
@@ -1094,6 +1103,7 @@ def main(ci_paths,
results = fold(StackResult, results, results = fold(StackResult, results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
sort=sort, sort=sort,
depth=depth) depth=depth)
@@ -1119,6 +1129,7 @@ def main(ci_paths,
diff_results = fold(StackResult, diff_results, diff_results = fold(StackResult, diff_results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
depth=depth) depth=depth)
# hotify? # hotify?
@@ -1226,6 +1237,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+26 -4
View File
@@ -634,6 +634,7 @@ class Rev(co.namedtuple('Rev', 'a')):
def fold(Result, results, *, def fold(Result, results, *,
by=None, by=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=1, depth=1,
**_): **_):
@@ -645,20 +646,27 @@ def fold(Result, results, *,
if by is None: if by is None:
by = Result._by by = Result._by
for k in it.chain(by or [], (k for k, _ in defines)): for k in it.chain(by or [],
(k for k, _ in defines),
(k for k, _ in undefines)):
if k not in Result._by and k not in Result._fields: if k not in Result._by and k not in Result._fields:
print("error: could not find field %r?" % k, print("error: could not find field %r?" % k,
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v) if not all(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(str(getattr(r, k, '')), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
# organize results into conflicts # organize results into conflicts
@@ -1196,6 +1204,7 @@ def main(obj_paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
sort=None, sort=None,
depth=None, depth=None,
hot=None, hot=None,
@@ -1245,6 +1254,7 @@ def main(obj_paths, *,
results = fold(StructResult, results, results = fold(StructResult, results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
sort=sort, sort=sort,
depth=depth) depth=depth)
@@ -1270,6 +1280,7 @@ def main(obj_paths, *,
diff_results = fold(StructResult, diff_results, diff_results = fold(StructResult, diff_results,
by=by, by=by,
defines=defines, defines=defines,
undefines=undefines,
depth=depth) depth=depth)
# hotify? # hotify?
@@ -1372,6 +1383,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
class AppendSort(argparse.Action): class AppendSort(argparse.Action):
def __call__(self, parser, namespace, value, option): def __call__(self, parser, namespace, value, option):
if namespace.sort is None: if namespace.sort is None:
+41 -9
View File
@@ -262,7 +262,9 @@ def dat(x, *args):
else: else:
raise raise
def collect(csv_paths, defines=[]): def collect(csv_paths, *,
defines=[],
undefines=[]):
# collect results from CSV files # collect results from CSV files
fields = [] fields = []
results = [] results = []
@@ -279,22 +281,34 @@ def collect(csv_paths, defines=[]):
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
continue continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results.append(r) results.append(r)
except FileNotFoundError: except FileNotFoundError:
pass pass
return fields, results return fields, results
def fold(results, by=None, fields=None, defines=[]): def fold(results, by=None, fields=None, *,
defines=[],
undefines=[]):
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(r.get(k, ''), v) if not all(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_ results = results_
if by: if by:
@@ -1017,6 +1031,7 @@ def main_(ring, csv_paths, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
labels=[], labels=[],
chars=[], chars=[],
colors=[], colors=[],
@@ -1087,7 +1102,9 @@ def main_(ring, csv_paths, *,
height_ = max(0, shutil.get_terminal_size((80, 5))[1] + height) height_ = max(0, shutil.get_terminal_size((80, 5))[1] + height)
# first collect results from CSV files # first collect results from CSV files
fields_, results = collect(csv_paths, defines) fields_, results = collect(csv_paths,
defines=defines,
undefines=undefines)
if not by and not fields: if not by and not fields:
print("error: needs --by or --fields to figure out fields", print("error: needs --by or --fields to figure out fields",
@@ -1098,16 +1115,20 @@ def main_(ring, csv_paths, *,
if not by: if not by:
by = [k for k in fields_ by = [k for k in fields_
if k not in (fields or []) if k not in (fields or [])
and not any(k == k_ for k_, _ in defines)] and not any(k == k_ for k_, _ in defines)
and not any(k == k_ for k_, _ in undefines)]
# if fields not specified, guess it's anything not in by/defines # if fields not specified, guess it's anything not in by/defines
if not fields: if not fields:
fields = [k for k in fields_ fields = [k for k in fields_
if k not in (by or []) if k not in (by or [])
and not any(k == k_ for k_, _ in defines)] and not any(k == k_ for k_, _ in defines)
and not any(k == k_ for k_, _ in undefines)]
# then extract the requested dataset # then extract the requested dataset
datasets, dataattrs = fold(results, by, fields, defines) datasets, dataattrs = fold(results, by, fields,
defines=defines,
undefines=undefines)
# build tile heirarchy # build tile heirarchy
children = [] children = []
@@ -1421,6 +1442,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
parser.add_argument( parser.add_argument(
'-L', '--add-label', '-L', '--add-label',
dest='labels', dest='labels',
+41 -9
View File
@@ -123,7 +123,9 @@ def dat(x, *args):
else: else:
raise raise
def collect(csv_paths, defines=[]): def collect(csv_paths, *,
defines=[],
undefines=[]):
# collect results from CSV files # collect results from CSV files
fields = [] fields = []
results = [] results = []
@@ -140,22 +142,34 @@ def collect(csv_paths, defines=[]):
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
continue continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results.append(r) results.append(r)
except FileNotFoundError: except FileNotFoundError:
pass pass
return fields, results return fields, results
def fold(results, by=None, fields=None, defines=[]): def fold(results, by=None, fields=None, *,
defines=[],
undefines=[]):
# filter by matching defines # filter by matching defines
if defines: if defines or undefines:
results_ = [] results_ = []
for r in results: for r in results:
if all(any(fnmatch.fnmatchcase(r.get(k, ''), v) if not all(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs) for v in vs)
for k, vs in defines): for k, vs in defines):
results_.append(r) continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in defines):
continue
results_.append(r)
results = results_ results = results_
if by: if by:
@@ -708,6 +722,7 @@ def main(csv_paths, output, *,
by=None, by=None,
fields=None, fields=None,
defines=[], defines=[],
undefines=[],
labels=[], labels=[],
colors=[], colors=[],
width=None, width=None,
@@ -761,7 +776,9 @@ def main(csv_paths, output, *,
height_ = HEIGHT height_ = HEIGHT
# first collect results from CSV files # first collect results from CSV files
fields_, results = collect(csv_paths, defines) fields_, results = collect(csv_paths,
defines=defines,
undefines=undefines)
if not by and not fields: if not by and not fields:
print("error: needs --by or --fields to figure out fields", print("error: needs --by or --fields to figure out fields",
@@ -772,16 +789,20 @@ def main(csv_paths, output, *,
if not by: if not by:
by = [k for k in fields_ by = [k for k in fields_
if k not in (fields or []) if k not in (fields or [])
and not any(k == k_ for k_, _ in defines)] and not any(k == k_ for k_, _ in defines)
and not any(k == k_ for k_, _ in undefines)]
# if fields not specified, guess it's anything not in by/labels/defines # if fields not specified, guess it's anything not in by/labels/defines
if not fields: if not fields:
fields = [k for k in fields_ fields = [k for k in fields_
if k not in (by or []) if k not in (by or [])
and not any(k == k_ for k_, _ in defines)] and not any(k == k_ for k_, _ in defines)
and not any(k == k_ for k_, _ in undefines)]
# then extract the requested dataset # then extract the requested dataset
datasets, dataattrs = fold(results, by, fields, defines) datasets, dataattrs = fold(results, by, fields,
defines=defines,
undefines=undefines)
# build tile heirarchy # build tile heirarchy
children = [] children = []
@@ -1117,6 +1138,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Only include results where this field is this value. May " help="Only include results where this field is this value. May "
"include comma-separated options and globs.") "include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
parser.add_argument( parser.add_argument(
'-L', '--add-label', '-L', '--add-label',
dest='labels', dest='labels',