From 84b2e73a30fa47989a8efd44583b99e66103bed6 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 22 Jan 2026 14:20:11 -0600 Subject: [PATCH] scripts: csv.py: Added enumerate/accumulate exprs This adds two new exprs to csv.py, useful for sequential data: enumerate() A number incremented each result accumulate(a) A running sum across results To make these work required adding support for cross-row state, thus the new state field in CsvExpr.Expr.eval. Once you have that cross-row state, implementing enumerate/accumulate is pretty straightforward. The only complication being that we need to hash state by the unique Python id (`id(self)`), otherwise multiple exprs would share state, which would be pretty weird. Note that csv.py's pipeline is now quite complex, and stage order is important! input --> define --> expr --> folding --> sorting --> output filtering eval As a result, it's unfortunately not possible to organize enumerate/ accumulate by by fields. I poked around with the idea but decided it was too complex (aren't I supposed be building a filesystem?). The guiding principle behind csv.py is most problems can be solved with more process substitution. --- This is a bit clunky since we can't use the existing fold system, but csv.py is already a pile of hacks, so what's one more? The reason for the clunkiness is that the original idea behind csv.py was to treat each folded row independently and order-agnostic. Not the greatest idea in hindsight, cross-row operations are useful! --- scripts/csv.py | 368 ++++++++++++++++++++++++------------------------- 1 file changed, 181 insertions(+), 187 deletions(-) diff --git a/scripts/csv.py b/scripts/csv.py index 26574bed..d612a0d3 100755 --- a/scripts/csv.py +++ b/scripts/csv.py @@ -540,8 +540,8 @@ class CsvExpr: def fold(self, types={}): return self.a.fold(types) - def eval(self, fields={}): - return self.a.eval(fields) + def eval(self, fields={}, state=None): + return self.a.eval(fields, state) # expr nodes @@ -556,7 +556,7 @@ class CsvExpr: def fold(self, types={}): return CsvSum, CsvInt - def eval(self, fields={}): + def eval(self, fields={}, state=None): return self.a class FloatLit(Expr): @@ -569,7 +569,7 @@ class CsvExpr: def fold(self, types={}): return CsvSum, CsvFloat - def eval(self, fields={}): + def eval(self, fields={}, state=None): return self.a # field expr @@ -587,7 +587,7 @@ class CsvExpr: raise CsvExpr.Error("unfoldable field? %s" % self.a) return CsvSum, types[self.a] - def eval(self, fields={}): + def eval(self, fields={}, state=None): if self.a not in fields: raise CsvExpr.Error("unknown field? %s" % self.a) return fields[self.a] @@ -613,8 +613,8 @@ class CsvExpr: def type(self, types={}): return CsvInt - def eval(self, fields={}): - return CsvInt(self.a.eval(fields)) + def eval(self, fields={}, state=None): + return CsvInt(self.a.eval(fields, state)) @func('float', 'a') class Float(Expr): @@ -622,8 +622,8 @@ class CsvExpr: def type(self, types={}): return CsvFloat - def eval(self, fields={}): - return CsvFloat(self.a.eval(fields)) + def eval(self, fields={}, state=None): + return CsvFloat(self.a.eval(fields, state)) @func('frac', 'a[, b]') class Frac(Expr): @@ -631,11 +631,14 @@ class CsvExpr: def type(self, types={}): return CsvFrac - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return CsvFrac(self.a.eval(fields)) + return CsvFrac( + self.a.eval(fields, state)) else: - return CsvFrac(self.a.eval(fields), self.b.eval(fields)) + return CsvFrac( + self.a.eval(fields, state), + self.b.eval(fields, state)) # fold exprs @func('sum', 'a[, ...]') @@ -647,11 +650,11 @@ class CsvExpr: else: return self.a.fold(types) - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return self.a.eval(fields) + return self.a.eval(fields, state) else: - return CsvSum()([v.eval(fields) for v in self]) + return CsvSum()([v.eval(fields, state) for v in self]) @func('prod', 'a[, ...]') class Prod(Expr): @@ -662,11 +665,11 @@ class CsvExpr: else: return self.a.fold(types) - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return self.a.eval(fields) + return self.a.eval(fields, state) else: - return Prod()([v.eval(fields) for v in self]) + return Prod()([v.eval(fields, state) for v in self]) @func('min', 'a[, ...]') class Min(Expr): @@ -677,11 +680,11 @@ class CsvExpr: else: return self.a.fold(types) - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return self.a.eval(fields) + return self.a.eval(fields, state) else: - return CsvMin()([v.eval(fields) for v in self]) + return CsvMin()([v.eval(fields, state) for v in self]) @func('max', 'a[, ...]') class Max(Expr): @@ -692,11 +695,11 @@ class CsvExpr: else: return self.a.fold(types) - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return self.a.eval(fields) + return self.a.eval(fields, state) else: - return CsvMax()([v.eval(fields) for v in self]) + return CsvMax()([v.eval(fields, state) for v in self]) @func('avg', 'a[, ...]') class Avg(Expr): @@ -713,11 +716,11 @@ class CsvExpr: else: return self.a.fold(types) - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return self.a.eval(fields) + return self.a.eval(fields, state) else: - return CsvAvg()([v.eval(fields) for v in self]) + return CsvAvg()([v.eval(fields, state) for v in self]) @func('stddev', 'a[, ...]') class Stddev(Expr): @@ -734,11 +737,11 @@ class CsvExpr: else: return self.a.fold(types) - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return self.a.eval(fields) + return self.a.eval(fields, state) else: - return CsvStddev()([v.eval(fields) for v in self]) + return CsvStddev()([v.eval(fields, state) for v in self]) @func('gmean', 'a[, ...]') class GMean(Expr): @@ -755,11 +758,11 @@ class CsvExpr: else: return self.a.fold(types) - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return self.a.eval(fields) + return self.a.eval(fields, state) else: - return CsvGMean()([v.eval(fields) for v in self]) + return CsvGMean()([v.eval(fields, state) for v in self]) @func('gstddev', 'a[, ...]') class GStddev(Expr): @@ -776,11 +779,54 @@ class CsvExpr: else: return self.a.fold(types) - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: - return self.a.eval(fields) + return self.a.eval(fields, state) else: - return CsvGStddev()([v.eval(fields) for v in self]) + return CsvGStddev()([v.eval(fields, state) for v in self]) + + # enumerate exprs + @func('enumerate', '') + class Enumerate(Expr): + """A number incremented each result""" + def fields(self): + return set() + + def type(self, types={}): + return CsvInt + + def fold(self, types={}): + return CsvSum, CsvInt + + def eval(self, fields={}, state=None): + if state is None: + return CsvInt(0) + # enumerate + v = state.get(('enumerate', id(self))) + if v is None: + v = 0 + else: + v += 1 + # keep track of unique enumerate state + state[('enumerate', id(self))] = v + return CsvInt(v) + + @func('accumulate', 'a') + class Accumulate(Expr): + """A running sum across results""" + def eval(self, fields={}, state=None): + v = self.a.eval(fields, state) + if state is None: + return v + # accumulate + v_ = state.get(('accumulate', id(self))) + if v_ is None: + v_ = v + else: + v_ += v + # keep track of unique accumulate state + state[('accumulate', id(self))] = v_ + return v_ # functions @func('ratio', 'a') @@ -789,8 +835,8 @@ class CsvExpr: def type(self, types={}): return CsvFloat - def eval(self, fields={}): - v = CsvFrac(self.a.eval(fields)) + def eval(self, fields={}, state=None): + v = CsvFrac(self.a.eval(fields, state)) if not float(v.b) and not float(v.a): return CsvFloat(1) elif not float(v.b): @@ -804,14 +850,14 @@ class CsvExpr: def type(self, types={}): return CsvInt - def eval(self, fields={}): - return CsvFrac(self.a.eval(fields)).b + def eval(self, fields={}, state=None): + return CsvFrac(self.a.eval(fields, state)).b @func('abs', 'a') class Abs(Expr): """Absolute value""" - def eval(self, fields={}): - return abs(self.a.eval(fields)) + def eval(self, fields={}, state=None): + return abs(self.a.eval(fields, state)) @func('ceil', 'a') class Ceil(Expr): @@ -819,8 +865,8 @@ class CsvExpr: def type(self, types={}): return CsvFloat - def eval(self, fields={}): - return CsvFloat(mt.ceil(float(self.a.eval(fields)))) + def eval(self, fields={}, state=None): + return CsvFloat(mt.ceil(float(self.a.eval(fields, state)))) @func('floor', 'a') class Floor(Expr): @@ -828,8 +874,8 @@ class CsvExpr: def type(self, types={}): return CsvFloat - def eval(self, fields={}): - return CsvFloat(mt.floor(float(self.a.eval(fields)))) + def eval(self, fields={}, state=None): + return CsvFloat(mt.floor(float(self.a.eval(fields, state)))) @func('log', 'a[, b]') class Log(Expr): @@ -837,14 +883,14 @@ class CsvExpr: def type(self, types={}): return CsvFloat - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: return CsvFloat(mt.log( - float(self.a.eval(fields)))) + float(self.a.eval(fields, state)))) else: return CsvFloat(mt.log( - float(self.a.eval(fields)), - float(self.b.eval(fields)))) + float(self.a.eval(fields, state)), + float(self.b.eval(fields, state)))) @func('pow', 'a[, b]') class Pow(Expr): @@ -852,14 +898,14 @@ class CsvExpr: def type(self, types={}): return CsvFloat - def eval(self, fields={}): + def eval(self, fields={}, state=None): if len(self) == 1: return CsvFloat(mt.exp( - float(self.a.eval(fields)))) + float(self.a.eval(fields, state)))) else: return CsvFloat(mt.pow( - float(self.a.eval(fields)), - float(self.b.eval(fields)))) + float(self.a.eval(fields, state)), + float(self.b.eval(fields, state)))) @func('sqrt', 'a') class Sqrt(Expr): @@ -867,8 +913,8 @@ class CsvExpr: def type(self, types={}): return CsvFloat - def eval(self, fields={}): - return CsvFloat(mt.sqrt(float(self.a.eval(fields)))) + def eval(self, fields={}, state=None): + return CsvFloat(mt.sqrt(float(self.a.eval(fields, state)))) @func('isint', 'a') class IsInt(Expr): @@ -876,8 +922,8 @@ class CsvExpr: def type(self, types={}): return CsvInt - def eval(self, fields={}): - if isinstance(self.a.eval(fields), CsvInt): + def eval(self, fields={}, state=None): + if isinstance(self.a.eval(fields, state), CsvInt): return CsvInt(1) else: return CsvInt(0) @@ -888,8 +934,8 @@ class CsvExpr: def type(self, types={}): return CsvInt - def eval(self, fields={}): - if isinstance(self.a.eval(fields), CsvFloat): + def eval(self, fields={}, state=None): + if isinstance(self.a.eval(fields, state), CsvFloat): return CsvInt(1) else: return CsvInt(0) @@ -900,8 +946,8 @@ class CsvExpr: def type(self, types={}): return CsvInt - def eval(self, fields={}): - if isinstance(self.a.eval(fields), CsvFrac): + def eval(self, fields={}, state=None): + if isinstance(self.a.eval(fields, state), CsvFrac): return CsvInt(1) else: return CsvInt(0) @@ -912,8 +958,8 @@ class CsvExpr: def type(self, types={}): return CsvInt - def eval(self, fields={}): - if mt.isinf(self.a.eval(fields)): + def eval(self, fields={}, state=None): + if mt.isinf(self.a.eval(fields, state)): return CsvInt(1) else: return CsvInt(0) @@ -924,8 +970,8 @@ class CsvExpr: def type(self, types={}): return CsvInt - def eval(self, fields={}): - if mt.isnan(self.a.eval(fields)): + def eval(self, fields={}, state=None): + if mt.isnan(self.a.eval(fields, state)): return CsvInt(1) else: return CsvInt(0) @@ -947,14 +993,14 @@ class CsvExpr: @uop('+') class Pos(Expr): """Non-negation""" - def eval(self, fields={}): - return +self.a.eval(fields) + def eval(self, fields={}, state=None): + return +self.a.eval(fields, state) @uop('-') class Neg(Expr): """Negation""" - def eval(self, fields={}): - return -self.a.eval(fields) + def eval(self, fields={}, state=None): + return -self.a.eval(fields, state) @uop('!') class NotNot(Expr): @@ -962,8 +1008,8 @@ class CsvExpr: def type(self, types={}): return CsvInt - def eval(self, fields={}): - if self.a.eval(fields): + def eval(self, fields={}, state=None): + if self.a.eval(fields, state): return CsvInt(0) else: return CsvInt(1) @@ -988,40 +1034,38 @@ class CsvExpr: @bop('*', 10) class Mul(Expr): """Multiplication""" - def eval(self, fields={}): - return self.a.eval(fields) * self.b.eval(fields) + def eval(self, fields={}, state=None): + return self.a.eval(fields, state) * self.b.eval(fields, state) @bop('/', 10) class Div(Expr): """Division""" - def eval(self, fields={}): - return self.a.eval(fields) / self.b.eval(fields) + def eval(self, fields={}, state=None): + return self.a.eval(fields, state) / self.b.eval(fields, state) @bop('%', 10) class Mod(Expr): """Modulo""" - def eval(self, fields={}): - return self.a.eval(fields) % self.b.eval(fields) + def eval(self, fields={}, state=None): + return self.a.eval(fields, state) % self.b.eval(fields, state) @bop('+', 9) class Add(Expr): """Addition""" - def eval(self, fields={}): - a = self.a.eval(fields) - b = self.b.eval(fields) - return a + b + def eval(self, fields={}, state=None): + return self.a.eval(fields, state) + self.b.eval(fields, state) @bop('-', 9) class Sub(Expr): """Subtraction""" - def eval(self, fields={}): - return self.a.eval(fields) - self.b.eval(fields) + def eval(self, fields={}, state=None): + return self.a.eval(fields, state) - self.b.eval(fields, state) @bop('==', 4) class Eq(Expr): """1 if a equals b, otherwise 0""" - def eval(self, fields={}): - if self.a.eval(fields) == self.b.eval(fields): + def eval(self, fields={}, state=None): + if self.a.eval(fields, state) == self.b.eval(fields, state): return CsvInt(1) else: return CsvInt(0) @@ -1029,8 +1073,8 @@ class CsvExpr: @bop('!=', 4) class Ne(Expr): """1 if a does not equal b, otherwise 0""" - def eval(self, fields={}): - if self.a.eval(fields) != self.b.eval(fields): + def eval(self, fields={}, state=None): + if self.a.eval(fields, state) != self.b.eval(fields, state): return CsvInt(1) else: return CsvInt(0) @@ -1038,8 +1082,8 @@ class CsvExpr: @bop('<', 4) class Lt(Expr): """1 if a is less than b""" - def eval(self, fields={}): - if self.a.eval(fields) < self.b.eval(fields): + def eval(self, fields={}, state=None): + if self.a.eval(fields, state) < self.b.eval(fields, state): return CsvInt(1) else: return CsvInt(0) @@ -1047,8 +1091,8 @@ class CsvExpr: @bop('<=', 4) class Le(Expr): """1 if a is less than or equal to b""" - def eval(self, fields={}): - if self.a.eval(fields) <= self.b.eval(fields): + def eval(self, fields={}, state=None): + if self.a.eval(fields, state) <= self.b.eval(fields, state): return CsvInt(1) else: return CsvInt(0) @@ -1056,8 +1100,8 @@ class CsvExpr: @bop('>', 4) class Gt(Expr): """1 if a is greater than b""" - def eval(self, fields={}): - if self.a.eval(fields) > self.b.eval(fields): + def eval(self, fields={}, state=None): + if self.a.eval(fields, state) > self.b.eval(fields, state): return CsvInt(1) else: return CsvInt(0) @@ -1065,8 +1109,8 @@ class CsvExpr: @bop('>=', 4) class Ge(Expr): """1 if a is greater than or equal to b""" - def eval(self, fields={}): - if self.a.eval(fields) >= self.b.eval(fields): + def eval(self, fields={}, state=None): + if self.a.eval(fields, state) >= self.b.eval(fields, state): return CsvInt(1) else: return CsvInt(0) @@ -1074,22 +1118,22 @@ class CsvExpr: @bop('&&', 3) class AndAnd(Expr): """b if a is non-zero, otherwise a""" - def eval(self, fields={}): - a = self.a.eval(fields) + def eval(self, fields={}, state=None): + a = self.a.eval(fields, state) if a: - return self.b.eval(fields) + return self.b.eval(fields, state) else: return a @bop('||', 2) class OrOr(Expr): """a if a is non-zero, otherwise b""" - def eval(self, fields={}): - a = self.a.eval(fields) + def eval(self, fields={}, state=None): + a = self.a.eval(fields, state) if a: return a else: - return self.b.eval(fields) + return self.b.eval(fields, state) # ternary expr help def top(tops, tprecs): @@ -1121,12 +1165,12 @@ class CsvExpr: def fold(self, types={}): return self.b.fold(types) - def eval(self, fields={}): - a = self.a.eval(fields) + def eval(self, fields={}, state=None): + a = self.a.eval(fields, state) if a: - return self.b.eval(fields) + return self.b.eval(fields, state) else: - return self.c.eval(fields) + return self.c.eval(fields, state) # show expr help text @classmethod @@ -1178,17 +1222,17 @@ class CsvExpr: raise CsvExpr.Error("unknown function? %s" % a) args = [] while True: - b = p_expr(p) - args.append(b) - if p.match(','): - p.chomp() - continue - else: - if not p.match('\)'): - raise CsvExpr.Error("mismatched parens? %s" % p) - p.chomp() - a = CsvExpr.funcs[a](*args) - break + if not p.match('\)'): + b = p_expr(p) + args.append(b) + if p.match(','): + p.chomp() + continue + if not p.match('\)'): + raise CsvExpr.Error("mismatched parens? %s" % p) + p.chomp() + a = CsvExpr.funcs[a](*args) + break else: a = CsvExpr.Field(a) @@ -1296,9 +1340,9 @@ class CsvExpr: sys.exit(3) # recursive evaluate the expr - def eval(self, fields={}): + def eval(self, fields={}, state=None): try: - return self.tree.eval(fields) + return self.tree.eval(fields, state) except CsvExpr.Error as e: print('error: in expr: %s' % self.expr, file=sys.stderr) @@ -1410,15 +1454,6 @@ def punescape_help(): '%(field)[fFeEgG]', 'An existing field formatted as a float')) -# a couple marker classes -class CsvEnumerate: - pass - -class CsvAccumulate: - def __init__(self, expr): - self.expr = expr - - # open with '-' for stdin/stdout def openio(path, mode='r', buffering=-1): import os @@ -1577,7 +1612,7 @@ def compile(fields_, results, folds___ = {k: (f(), t) for k, (f, t) in folds___.items()} # create result class - def __new__(cls, **r): + def __new__(cls, _state=None, **r): r_ = r.copy() # evaluate types, strip prefix for k, t in types__.items(): @@ -1586,7 +1621,7 @@ def compile(fields_, results, r__ = r_.copy() # evaluate exprs for k, expr in exprs.items(): - r__[k] = expr.eval(r_) + r__[k] = expr.eval(r_, _state) # evaluate mods for k, m in mods.items(): r__[k] = punescape(m, r_) @@ -1656,18 +1691,12 @@ def compile(fields_, results, def homogenize(Result, results, *, enumerates=None, - accumulates=None, defines=[], depth=1, **_): + # running result state + state = {} # convert all (possibly recursive) results to our result type - - # prepare accumulators - accumulators = { - k: {'i': CsvInt(0), 'a': CsvInt(0), 'b': CsvInt(0), 'i': CsvInt(0)} - for k, v in accumulates} - - # homogenize results results_ = [] for r in results: # filter by matching defines @@ -1682,26 +1711,24 @@ def homogenize(Result, results, *, continue # append a result - results_.append(Result(**( - r + results_.append(Result( + **(r # enumerate? | ({e: len(results_) for e in enumerates} if enumerates is not None else {}) - # accumulate? - # TODO # recurse? | ({Result._children: homogenize( Result, r[Result._children], # only filter defines at the top level! enumerates=enumerates, - accumulates=accumulates, depth=depth-1)} if hasattr(Result, '_children') and Result._children in r and r[Result._children] is not None and depth > 1 - else {})))) + else {})), + _state=state)) return results_ @@ -2366,25 +2393,20 @@ def main(csv_paths, *, # separate out enumerates/mods/exprs # # enumerate enumerates: -ia - # accumulate supports exprs: -ga=0.99*g+0.01*b # by supports mods: -ba=%(b)s # fields/sort/etc supports exprs: -fa=b+c # enumerates = [k for (k, v), hidden in (by or []) - if isinstance(v, CsvEnumerate)] - accumulates = [(k, v) - for (k, v), hidden in (fields or []) - if isinstance(v, CsvAccumulate)] + if v == enumerate] mods = [(k, v) for k, v in it.chain( ((k, v) for (k, v), hidden in (by or []) - if not isinstance(v, CsvEnumerate))) + if v != enumerate)) if v is not None] exprs = [(k, v) for k, v in it.chain( - ((k, v) for (k, v), hidden in (fields or []) - if not isinstance(v, CsvAccumulate)), + ((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 (hot or []))) if v is not None] @@ -2457,7 +2479,6 @@ def main(csv_paths, *, # homogenize results = homogenize(Result, results, enumerates=enumerates, - accumulates=accumulates, defines=defines, depth=depth) @@ -2495,7 +2516,6 @@ def main(csv_paths, *, # homogenize diff_results = homogenize(Result, diff_results, enumerates=enumerates, - accumulates=accumulates, defines=defines, depth=depth) @@ -2594,16 +2614,16 @@ if __name__ == "__main__": '-i', '--enumerate', action=AppendBy, nargs='?', - type=lambda x: (x, CsvEnumerate()), - const=('i', CsvEnumerate()), - help="Enumerate results with this field. This will prevent " - "result folding.") + type=lambda x: (x, enumerate), + const=('i', enumerate), + help="Enumerate results with this field, equivalent to " + " -bi -Fi=enumerate(). This will prevent result folding.") parser.add_argument( '-I', '--hidden-enumerate', action=AppendBy, nargs='?', - type=lambda x: (x, CsvEnumerate()), - const=('i', CsvEnumerate()), + type=lambda x: (x, enumerate), + const=('i', enumerate), help="Like -i/--enumerate, but hidden from the table renderer, " "and doesn't affect -b/--by defaults.") parser.add_argument( @@ -2631,8 +2651,7 @@ if __name__ == "__main__": if namespace.fields is None: namespace.fields = [] namespace.fields.append((value, option in { - '-F', '--hidden-field', - '-G', '--hidden-accumulate'})) + '-F', '--hidden-field'})) parser.add_argument( '-f', '--field', dest='fields', @@ -2655,31 +2674,6 @@ if __name__ == "__main__": )(*x.split('=', 1)), help="Like -f/--field, but hidden from the table renderer, " "and doesn't affect -f/--field defaults.") - parser.add_argument( - '-g', '--accumulate', - dest='fields', - action=AppendField, - type=lambda x: ( - lambda k, v=None: ( - k.strip(), - CsvAccumulate(CsvExpr(v) if v is not None else None)) - )(*x.split('=', 1)), - help="Accumulate this field. Note accumulation is dependent " - "on input row order, and may need a second pass after " - "sorting. Can include an expression, but the expression " - "is evaluated early with i, a, b, and g as arguments. " - "Default behavior matches the expression (i==0)?b:g+b.") - parser.add_argument( - '-G', '--hidden-accumulate', - dest='fields', - action=AppendField, - type=lambda x: ( - lambda k, v=None: ( - k.strip(), - CsvAccumulate(CsvExpr(v) if v is not None else None)) - )(*x.split('=', 1)), - help="Like -g/--accumulate, but hidden from the table renderer, " - "and doesn't affect -f/--field defaults.") parser.add_argument( '-D', '--define', dest='defines',