scripts: csv.py: Replaced -l/--label with -I/-B/-F for hidden fields

It felt weird that adding hidden fields required changing existing
flags unrelated to the field you actually want to affect, and the
upper/lower flag thing seems to work well for -s/-S sooo...

- Replaced -l/--label with -B/--hidden-by for by fields that can
  be hidden from the table renderer.

- Added -F/--hidden-field as a similar thing for field fields.

- Better integrated -i/--enumerate into by fields, now these actually
  maintain related order. And of course added a matching
  -I/--hidden-enumerate flag.

The only downside is this is eating a lot of flag names.. But one of the
nice thing about limiting this complexity to csv.py is it avoids these
flag names cluttering up the other result scripts.

---

The -F/--hidden-fields flag I'm not so sure about, since field exprs
can't really reference each other (single pass). But it does provide
symmetry with -B/--hidden-by, and reserves the name in case hidden field
fields are more useful in the future.

Unfortunately it _is_ annoyingly inconsistent with other hidden fields
(-S/--sort, -D/--define, etc) in that it does end up in output csvs...

But this script is already feeling way over-engineered as is.
This commit is contained in:
Christopher Haster
2025-02-28 19:23:11 -06:00
parent 1b8733b3de
commit 5811b11131
6 changed files with 117 additions and 106 deletions
+82 -71
View File
@@ -1561,11 +1561,8 @@ def compile(fields_, results,
**{'_notes': notes} if notes is not None else {})) **{'_notes': notes} if notes is not None else {}))
def homogenize(Result, results, *, def homogenize(Result, results, *,
enumerate=None, enumerates=None,
depth=1): depth=1):
import builtins
enumerate_, enumerate = enumerate, builtins.enumerate
# this just converts all (possibly recursive) results to our # this just converts all (possibly recursive) results to our
# result type # result type
results_ = [] results_ = []
@@ -1573,13 +1570,13 @@ def homogenize(Result, results, *,
results_.append(Result(**( results_.append(Result(**(
r r
# enumerate? # enumerate?
| ({enumerate_: i} | ({e: i for e in enumerates}
if enumerate_ is not None if enumerates is not None
else {}) else {})
# recurse? # recurse?
| ({Result._children: homogenize( | ({Result._children: homogenize(
Result, r[Result._children], Result, r[Result._children],
enumerate=enumerate_, enumerates=enumerates,
depth=depth-1)} depth=depth-1)}
if hasattr(Result, '_children') if hasattr(Result, '_children')
and Result._children in r and Result._children in r
@@ -1672,14 +1669,12 @@ def fold(Result, results, *,
return folded return folded
def hotify(Result, results, *, def hotify(Result, results, *,
enumerate=None, enumerates=None,
depth=1, depth=1,
hot=None, hot=None,
**_): **_):
# note! hotifying risks confusion if you don't enumerate/have a z # note! hotifying risks confusion if you don't enumerate/have a
# field, since it will allow folding across recursive boundaries # z field, since it will allow folding across recursive boundaries
import builtins
enumerate_, enumerate = enumerate, builtins.enumerate
# hotify only makes sense for recursive results # hotify only makes sense for recursive results
assert hasattr(Result, '_children') assert hasattr(Result, '_children')
@@ -1704,8 +1699,10 @@ def hotify(Result, results, *,
for k, reverse in it.chain(hot, [(None, False)]))) for k, reverse in it.chain(hot, [(None, False)])))
hot_.append(r._replace(**( hot_.append(r._replace(**(
({enumerate_: len(hot_)} # enumerate?
if enumerate_ is not None else {}) ({e: len(hot_) for e in enumerates}
if enumerates is not None
else {})
| {Result._children: []}))) | {Result._children: []})))
# recurse? # recurse?
@@ -2134,16 +2131,11 @@ def main(csv_paths, *,
fields=None, fields=None,
defines=[], defines=[],
sort=None, sort=None,
enumerate=None,
labels=None,
depth=None, depth=None,
children=None, children=None,
hot=None, hot=None,
notes=None, notes=None,
**args): **args):
import builtins
enumerate_, enumerate = enumerate, builtins.enumerate
# show mod help text? # show mod help text?
if args.get('help_mods'): if args.get('help_mods'):
return punescape_help() return punescape_help()
@@ -2151,18 +2143,11 @@ def main(csv_paths, *,
if args.get('help_exprs'): if args.get('help_exprs'):
return RExpr.help() return RExpr.help()
if by is None and enumerate_ is None and labels is None and fields is None: if by is None and fields is None:
print("error: needs --by or --fields to figure out fields", print("error: needs --by or --fields to figure out fields",
file=sys.stderr) file=sys.stderr)
sys.exit(-1) sys.exit(-1)
if enumerate_ is not None:
if len(enumerate_) > 1:
print("error: multiple --enumerate fields currently not supported",
file=sys.stderr)
sys.exit(-1)
enumerate_ = enumerate_[0]
if children is not None: if children is not None:
if len(children) > 1: if len(children) > 1:
print("error: multiple --children fields currently not supported", print("error: multiple --children fields currently not supported",
@@ -2187,43 +2172,39 @@ def main(csv_paths, *,
elif depth == 0: elif depth == 0:
depth = mt.inf depth = mt.inf
# separate out mods/exprs # separate out enumerates/mods/exprs
# #
# by supports mods => -ba=%(b)s # enumerate enumerates: -ia
# fields/sort/hot support exprs => -fa=b+c # by supports mods: -ba=%(b)s
# fields/sort/etc supports exprs: -fa=b+c
#
enumerates = [k
for (k, v), hidden in (by or [])
if v == enumerate]
mods = [(k, v) mods = [(k, v)
for k, v in it.chain( for k, v in it.chain(
by or [], ((k, v) for (k, v), hidden in (by or [])
labels or []) if v != enumerate))
if v is not None] if v is not None]
exprs = [(k, v) exprs = [(k, v)
for k, v in it.chain( for k, v in it.chain(
fields or [], ((k, v) for (k, v), hidden in (fields or [])),
((k, v) for (k, v), reverse in (sort or [])), ((k, v) for (k, v), reverse in (sort or [])),
((k, v) for (k, v), reverse in (hot or []))) ((k, v) for (k, v), reverse in (hot or [])))
if v is not None] if v is not None]
labels = None
if by is not None: if by is not None:
by = [k for k, _ in by] labels = [k for (k, v), hidden in by if not hidden]
by = [k for (k, v), hidden in by]
visible = None
if fields is not None: if fields is not None:
fields = [k for k, _ in fields] visible = [k for (k, v), hidden in fields if not hidden]
fields = [k for (k, v), hidden in fields]
if sort is not None: if sort is not None:
sort = [(k, reverse) for (k, v), reverse in sort] sort = [(k, reverse) for (k, v), reverse in sort]
if labels is not None:
labels = [k for k, _ in labels]
if hot is not None: if hot is not None:
hot = [(k, reverse) for (k, v), reverse in hot] hot = [(k, reverse) for (k, v), reverse in hot]
# include enumerate and label fields in by
if enumerate_ is not None:
by = by or []
if enumerate_ not in by:
by.insert(0, enumerate_)
if labels is not None:
by = by or []
for k in labels:
if k not in by:
by.append(k)
# find results # find results
if not args.get('use', None): if not args.get('use', None):
# not enough info? # not enough info?
@@ -2287,7 +2268,7 @@ def main(csv_paths, *,
# homogenize # homogenize
results = homogenize(Result, results, results = homogenize(Result, results,
enumerate=enumerate_, enumerates=enumerates,
depth=depth) depth=depth)
# fold # fold
@@ -2299,7 +2280,7 @@ def main(csv_paths, *,
# hotify? # hotify?
if hot: if hot:
results = hotify(Result, results, results = hotify(Result, results,
enumerate=enumerate_, enumerates=enumerates,
depth=depth, depth=depth,
hot=hot) hot=hot)
@@ -2338,8 +2319,9 @@ def main(csv_paths, *,
# print table # print table
if not args.get('quiet'): if not args.get('quiet'):
table(Result, results, diff_results, table(Result, results, diff_results,
# note the use of labels + visible here
by=by, by=by,
fields=fields, fields=visible if visible is not None else fields,
sort=sort, sort=sort,
labels=labels, labels=labels,
depth=depth, depth=depth,
@@ -2393,9 +2375,31 @@ if __name__ == "__main__":
'-a', '--all', '-a', '--all',
action='store_true', action='store_true',
help="Show all, not just the ones that changed.") help="Show all, not just the ones that changed.")
class AppendBy(argparse.Action):
def __call__(self, parser, namespace, value, option):
if namespace.by is None:
namespace.by = []
namespace.by.append((value, option in {
'-B', '--hidden-by',
'-I', '--hidden-enumerate'}))
parser.add_argument(
'-i', '--enumerate',
action=AppendBy,
nargs='?',
type=lambda x: (x, enumerate),
const=('i', enumerate),
help="Enumerate results with this field. This will prevent "
"result folding.")
parser.add_argument(
'-I', '--hidden-enumerate',
action=AppendBy,
nargs='?',
type=lambda x: (x, enumerate),
const=('i', enumerate),
help="Like -i/--enumerate, but hidden from the table renderer.")
parser.add_argument( parser.add_argument(
'-b', '--by', '-b', '--by',
action='append', action=AppendBy,
type=lambda x: ( type=lambda x: (
lambda k, v=None: ( lambda k, v=None: (
k.strip(), k.strip(),
@@ -2403,10 +2407,25 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Group by this field. This does _not_ support expressions, " help="Group by this field. This does _not_ support expressions, "
"but can be assigned a string with %% modifiers.") "but can be assigned a string with %% modifiers.")
parser.add_argument(
'-B', '--hidden-by',
action=AppendBy,
type=lambda x: (
lambda k, v=None: (
k.strip(),
v.strip() if v is not None else None)
)(*x.split('=', 1)),
help="Like -b/--by, but hidden from the table renderer.")
class AppendField(argparse.Action):
def __call__(self, parser, namespace, value, option):
if namespace.fields is None:
namespace.fields = []
namespace.fields.append((value, option in {
'-F', '--hidden-field'}))
parser.add_argument( parser.add_argument(
'-f', '--field', '-f', '--field',
dest='fields', dest='fields',
action='append', action=AppendField,
type=lambda x: ( type=lambda x: (
lambda k, v=None: ( lambda k, v=None: (
k.strip(), k.strip(),
@@ -2414,6 +2433,16 @@ if __name__ == "__main__":
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Show this field. Can include an expression of the form " help="Show this field. Can include an expression of the form "
"field=expr.") "field=expr.")
parser.add_argument(
'-F', '--hidden-field',
dest='fields',
action=AppendField,
type=lambda x: (
lambda k, v=None: (
k.strip(),
v.strip() if v is not None else None)
)(*x.split('=', 1)),
help="Like -f/--field, but hidden from the table renderer.")
parser.add_argument( parser.add_argument(
'-D', '--define', '-D', '--define',
dest='defines', dest='defines',
@@ -2454,24 +2483,6 @@ if __name__ == "__main__":
const=(None, None), const=(None, None),
help="Sort by this field, but backwards. Can include an expression " help="Sort by this field, but backwards. Can include an expression "
"of the form field=expr.") "of the form field=expr.")
parser.add_argument(
'-i', '--enumerate',
nargs='?',
const='i',
action='append',
help="Field to use for enumerating results. This will prevent "
"result folding.")
parser.add_argument(
'-l', '--label',
dest='labels',
action='append',
type=lambda x: (
lambda k, v=None: (
k.strip(),
v.strip() if v is not None else None)
)(*x.split('=', 1)),
help="Field to use for labeling results. This defaults to all "
"-b/--by fields. Can be assigned a string with %% modifiers.")
parser.add_argument( parser.add_argument(
'-z', '--depth', '-z', '--depth',
nargs='?', nargs='?',
+7 -7
View File
@@ -791,14 +791,12 @@ def fold(Result, results, *,
return folded return folded
def hotify(Result, results, *, def hotify(Result, results, *,
enumerate=None, enumerates=None,
depth=1, depth=1,
hot=None, hot=None,
**_): **_):
# note! hotifying risks confusion if you don't enumerate/have a z # note! hotifying risks confusion if you don't enumerate/have a
# field, since it will allow folding across recursive boundaries # z field, since it will allow folding across recursive boundaries
import builtins
enumerate_, enumerate = enumerate, builtins.enumerate
# hotify only makes sense for recursive results # hotify only makes sense for recursive results
assert hasattr(Result, '_children') assert hasattr(Result, '_children')
@@ -823,8 +821,10 @@ def hotify(Result, results, *,
for k, reverse in it.chain(hot, [(None, False)]))) for k, reverse in it.chain(hot, [(None, False)])))
hot_.append(r._replace(**( hot_.append(r._replace(**(
({enumerate_: len(hot_)} # enumerate?
if enumerate_ is not None else {}) ({e: len(hot_) for e in enumerates}
if enumerates is not None
else {})
| {Result._children: []}))) | {Result._children: []})))
# recurse? # recurse?
+7 -7
View File
@@ -895,14 +895,12 @@ def fold(Result, results, *,
return folded return folded
def hotify(Result, results, *, def hotify(Result, results, *,
enumerate=None, enumerates=None,
depth=1, depth=1,
hot=None, hot=None,
**_): **_):
# note! hotifying risks confusion if you don't enumerate/have a z # note! hotifying risks confusion if you don't enumerate/have a
# field, since it will allow folding across recursive boundaries # z field, since it will allow folding across recursive boundaries
import builtins
enumerate_, enumerate = enumerate, builtins.enumerate
# hotify only makes sense for recursive results # hotify only makes sense for recursive results
assert hasattr(Result, '_children') assert hasattr(Result, '_children')
@@ -927,8 +925,10 @@ def hotify(Result, results, *,
for k, reverse in it.chain(hot, [(None, False)]))) for k, reverse in it.chain(hot, [(None, False)])))
hot_.append(r._replace(**( hot_.append(r._replace(**(
({enumerate_: len(hot_)} # enumerate?
if enumerate_ is not None else {}) ({e: len(hot_) for e in enumerates}
if enumerates is not None
else {})
| {Result._children: []}))) | {Result._children: []})))
# recurse? # recurse?
+7 -7
View File
@@ -865,14 +865,12 @@ def fold(Result, results, *,
return folded return folded
def hotify(Result, results, *, def hotify(Result, results, *,
enumerate=None, enumerates=None,
depth=1, depth=1,
hot=None, hot=None,
**_): **_):
# note! hotifying risks confusion if you don't enumerate/have a z # note! hotifying risks confusion if you don't enumerate/have a
# field, since it will allow folding across recursive boundaries # z field, since it will allow folding across recursive boundaries
import builtins
enumerate_, enumerate = enumerate, builtins.enumerate
# hotify only makes sense for recursive results # hotify only makes sense for recursive results
assert hasattr(Result, '_children') assert hasattr(Result, '_children')
@@ -897,8 +895,10 @@ def hotify(Result, results, *,
for k, reverse in it.chain(hot, [(None, False)]))) for k, reverse in it.chain(hot, [(None, False)])))
hot_.append(r._replace(**( hot_.append(r._replace(**(
({enumerate_: len(hot_)} # enumerate?
if enumerate_ is not None else {}) ({e: len(hot_) for e in enumerates}
if enumerates is not None
else {})
| {Result._children: []}))) | {Result._children: []})))
# recurse? # recurse?
+7 -7
View File
@@ -537,14 +537,12 @@ def fold(Result, results, *,
return folded return folded
def hotify(Result, results, *, def hotify(Result, results, *,
enumerate=None, enumerates=None,
depth=1, depth=1,
hot=None, hot=None,
**_): **_):
# note! hotifying risks confusion if you don't enumerate/have a z # note! hotifying risks confusion if you don't enumerate/have a
# field, since it will allow folding across recursive boundaries # z field, since it will allow folding across recursive boundaries
import builtins
enumerate_, enumerate = enumerate, builtins.enumerate
# hotify only makes sense for recursive results # hotify only makes sense for recursive results
assert hasattr(Result, '_children') assert hasattr(Result, '_children')
@@ -569,8 +567,10 @@ def hotify(Result, results, *,
for k, reverse in it.chain(hot, [(None, False)]))) for k, reverse in it.chain(hot, [(None, False)])))
hot_.append(r._replace(**( hot_.append(r._replace(**(
({enumerate_: len(hot_)} # enumerate?
if enumerate_ is not None else {}) ({e: len(hot_) for e in enumerates}
if enumerates is not None
else {})
| {Result._children: []}))) | {Result._children: []})))
# recurse? # recurse?
+7 -7
View File
@@ -611,14 +611,12 @@ def fold(Result, results, *,
return folded return folded
def hotify(Result, results, *, def hotify(Result, results, *,
enumerate=None, enumerates=None,
depth=1, depth=1,
hot=None, hot=None,
**_): **_):
# note! hotifying risks confusion if you don't enumerate/have a z # note! hotifying risks confusion if you don't enumerate/have a
# field, since it will allow folding across recursive boundaries # z field, since it will allow folding across recursive boundaries
import builtins
enumerate_, enumerate = enumerate, builtins.enumerate
# hotify only makes sense for recursive results # hotify only makes sense for recursive results
assert hasattr(Result, '_children') assert hasattr(Result, '_children')
@@ -643,8 +641,10 @@ def hotify(Result, results, *,
for k, reverse in it.chain(hot, [(None, False)]))) for k, reverse in it.chain(hot, [(None, False)])))
hot_.append(r._replace(**( hot_.append(r._replace(**(
({enumerate_: len(hot_)} # enumerate?
if enumerate_ is not None else {}) ({e: len(hot_) for e in enumerates}
if enumerates is not None
else {})
| {Result._children: []}))) | {Result._children: []})))
# recurse? # recurse?