scripts: csv.py: Extended -s/-S to support exprs and hidden fields

The main benefit of this is allowing the sort order to be controlled by
fields that don't necessarily need to be printed:

  ./scripts/csv.py input.csv -ba -sb -fc

By default this sorts lexicographically, but this can be changed by
providing an expression:

  ./scripts/csv.py input.csv -ba -sb='int(b)' -fc

Note that sort fields do _not_ change inferred by fields, this allows
sort flags to be added to existing queries without changing the results
too much:

  ./scripts/csv.py input.csv -fc
  ./scripts/csv.py input.csv -sb -fc
This commit is contained in:
Christopher Haster
2024-11-13 23:32:58 -06:00
parent bac59d4928
commit 9e7e79390a
+29 -5
View File
@@ -1225,7 +1225,8 @@ def infer(fields_, results,
by=None, by=None,
fields=None, fields=None,
exprs=[], exprs=[],
defines=[]): defines=[],
sort=None):
# we only really care about the last expr for each field # we only really care about the last expr for each field
exprs = {k: expr for k, expr in exprs} exprs = {k: expr for k, expr in exprs}
@@ -1251,6 +1252,11 @@ def infer(fields_, results,
by = list(co.OrderedDict.fromkeys(by).keys()) by = list(co.OrderedDict.fromkeys(by).keys())
fields = list(co.OrderedDict.fromkeys(fields).keys()) fields = list(co.OrderedDict.fromkeys(fields).keys())
# make sure sort fields are included
if sort is not None:
by.extend(k for k, reverse in sort
if k not in by and k not in fields)
# find best type for all fields used by field exprs # find best type for all fields used by field exprs
fields__ = set(it.chain.from_iterable( fields__ = set(it.chain.from_iterable(
exprs[k].fields() if k in exprs else {k} exprs[k].fields() if k in exprs else {k}
@@ -1568,12 +1574,17 @@ def main(csv_paths, *,
# separate out exprs # separate out exprs
exprs = [(k, v) exprs = [(k, v)
for k, v in it.chain(by or [], fields or []) for k, v in it.chain(
by or [],
fields or [],
((k, v) for (k, v), reverse in sort or []))
if v is not None] if v is not None]
if by is not None: if by is not None:
by = [k for k, _ in by] by = [k for k, _ in by]
if fields is not None: if fields is not None:
fields = [k for k, _ in fields] fields = [k for k, _ in fields]
if sort is not None:
sort = [(k, reverse) for (k, v), reverse in sort]
if by 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",
@@ -1592,7 +1603,8 @@ def main(csv_paths, *,
by=by, by=by,
fields=fields, fields=fields,
exprs=exprs, exprs=exprs,
defines=defines) defines=defines,
sort=sort)
results_ = [] results_ = []
for r in results: for r in results:
results_.append(Result(**{ results_.append(Result(**{
@@ -1728,12 +1740,24 @@ if __name__ == "__main__":
'-s', '--sort', '-s', '--sort',
nargs='?', nargs='?',
action=AppendSort, action=AppendSort,
help="Sort by this field.") type=lambda x: (
lambda k, v=None: (
k.strip(),
RExpr(v) if v is not None else None)
)(*x.split('=', 1)),
help="Sort by this field. Can include an expression of the form "
"field=expr.")
parser.add_argument( parser.add_argument(
'-S', '--reverse-sort', '-S', '--reverse-sort',
nargs='?', nargs='?',
action=AppendSort, action=AppendSort,
help="Sort by this field, but backwards.") type=lambda x: (
lambda k, v=None: (
k.strip(),
RExpr(v) if v is not None else None)
)(*x.split('=', 1)),
help="Sort by this field, but backwards. Can include an expression "
"of the form field=expr.")
parser.add_argument( parser.add_argument(
'-Y', '--summary', '-Y', '--summary',
action='store_true', action='store_true',