diff --git a/Makefile b/Makefile index 84aeb18f..e848e9c4 100644 --- a/Makefile +++ b/Makefile @@ -170,10 +170,10 @@ coverage: $(GCDA) .PHONY: summary sizes summary sizes: $(BUILDDIR)lfs.csv $(strip ./scripts/summary.py -Y $^ \ - -fcode=code_size,$\ - data=data_size,$\ - stack=stack_limit,$\ - struct=struct_size \ + -fcode=code_size \ + -fdata=data_size \ + -fstack=stack_limit \ + -fstruct=struct_size \ --max=stack \ $(SUMMARYFLAGS)) diff --git a/runners/bench_runner.c b/runners/bench_runner.c index 073760f7..f7e33479 100644 --- a/runners/bench_runner.c +++ b/runners/bench_runner.c @@ -90,9 +90,7 @@ static uintmax_t leb16_parse(const char *s, char **tail) { // bench_runner types typedef struct bench_geometry { - char short_name; - const char *long_name; - + const char *name; bench_define_t defines[BENCH_GEOMETRY_DEFINE_COUNT]; } bench_geometry_t; @@ -1057,7 +1055,7 @@ static void list_implicit_defines(void) { // make sure to include builtin geometries here extern const bench_geometry_t builtin_geometries[]; - for (size_t g = 0; builtin_geometries[g].long_name; g++) { + for (size_t g = 0; builtin_geometries[g].name; g++) { bench_define_geometry(&builtin_geometries[g]); bench_define_flush(); @@ -1089,12 +1087,12 @@ static void list_implicit_defines(void) { // geometries to bench const bench_geometry_t builtin_geometries[] = { - {'d', "default", {{NULL}, BENCH_CONST(16), BENCH_CONST(512), {NULL}}}, - {'e', "eeprom", {{NULL}, BENCH_CONST(1), BENCH_CONST(512), {NULL}}}, - {'E', "emmc", {{NULL}, {NULL}, BENCH_CONST(512), {NULL}}}, - {'n', "nor", {{NULL}, BENCH_CONST(1), BENCH_CONST(4096), {NULL}}}, - {'N', "nand", {{NULL}, BENCH_CONST(4096), BENCH_CONST(32768), {NULL}}}, - {0, NULL, {{NULL}, {NULL}, {NULL}, {NULL}}}, + {"default", {{NULL}, BENCH_CONST(16), BENCH_CONST(512), {NULL}}}, + {"eeprom", {{NULL}, BENCH_CONST(1), BENCH_CONST(512), {NULL}}}, + {"emmc", {{NULL}, {NULL}, BENCH_CONST(512), {NULL}}}, + {"nor", {{NULL}, BENCH_CONST(1), BENCH_CONST(4096), {NULL}}}, + {"nand", {{NULL}, BENCH_CONST(4096), BENCH_CONST(32768), {NULL}}}, + {NULL, {{NULL}, {NULL}, {NULL}, {NULL}}}, }; const bench_geometry_t *bench_geometries = builtin_geometries; @@ -1107,12 +1105,11 @@ static void list_geometries(void) { printf("%-24s %7s %7s %7s %7s %11s\n", "geometry", "read", "prog", "erase", "count", "size"); - for (size_t g = 0; builtin_geometries[g].long_name; g++) { + for (size_t g = 0; builtin_geometries[g].name; g++) { bench_define_geometry(&builtin_geometries[g]); bench_define_flush(); - printf("%c,%-22s %7ju %7ju %7ju %7ju %11ju\n", - builtin_geometries[g].short_name, - builtin_geometries[g].long_name, + printf("%-24s %7ju %7ju %7ju %7ju %11ju\n", + builtin_geometries[g].name, READ_SIZE, PROG_SIZE, BLOCK_SIZE, @@ -1253,7 +1250,7 @@ enum opt_flags { OPT_LIST_IMPLICIT_DEFINES = 5, OPT_LIST_GEOMETRIES = 6, OPT_DEFINE = 'D', - OPT_GEOMETRY = 'g', + OPT_GEOMETRY = 'G', OPT_STEP = 's', OPT_DISK = 'd', OPT_TRACE = 't', @@ -1262,7 +1259,7 @@ enum opt_flags { OPT_ERASE_SLEEP = 9, }; -const char *short_opts = "hYlLD:g:s:d:t:"; +const char *short_opts = "hYlLD:G:s:d:t:"; const struct option long_opts[] = { {"help", no_argument, NULL, OPT_HELP}, @@ -1300,7 +1297,7 @@ const char *const help_text[] = { "List implicit defines in this bench-runner.", "List the available disk geometries.", "Override a bench define.", - "Comma-separated list of disk geometries to bench. Defaults to d,e,E,n,N.", + "Comma-separated list of disk geometries to bench.", "Comma-separated range of bench permutations to run (start,stop,step).", "Redirect block device operations to this file.", "Redirect trace output to this file.", @@ -1555,14 +1552,11 @@ invalid_define: // named disk geometry size_t len = strcspn(optarg, " ,"); - for (size_t i = 0; builtin_geometries[i].long_name; i++) { - if ((len == 1 - && *optarg == builtin_geometries[i].short_name) - || (len == strlen( - builtin_geometries[i].long_name) - && memcmp(optarg, - builtin_geometries[i].long_name, - len) == 0)) { + for (size_t i = 0; builtin_geometries[i].name; i++) { + if (len == strlen(builtin_geometries[i].name) + && memcmp(optarg, + builtin_geometries[i].name, + len) == 0) { *geometry = builtin_geometries[i]; optarg += len; goto geometry_next; diff --git a/runners/test_runner.c b/runners/test_runner.c index 34c31e1e..f0e1ff6e 100644 --- a/runners/test_runner.c +++ b/runners/test_runner.c @@ -90,16 +90,12 @@ static uintmax_t leb16_parse(const char *s, char **tail) { // test_runner types typedef struct test_geometry { - char short_name; - const char *long_name; - + const char *name; test_define_t defines[TEST_GEOMETRY_DEFINE_COUNT]; } test_geometry_t; typedef struct test_powerloss { - char short_name; - const char *long_name; - + const char *name; void (*run)( const lfs_emubd_powercycles_t *cycles, size_t cycle_count, @@ -574,6 +570,11 @@ void test_seen_cleanup(test_seen_t *seen) { free(seen->branches); } +static void run_powerloss_none( + const lfs_emubd_powercycles_t *cycles, + size_t cycle_count, + const struct test_suite *suite, + const struct test_case *case_); static void run_powerloss_cycles( const lfs_emubd_powercycles_t *cycles, size_t cycle_count, @@ -606,7 +607,7 @@ static void case_forperm( } else { for (size_t p = 0; p < test_powerloss_count; p++) { // skip non-reentrant tests when powerloss testing - if (test_powerlosses[p].short_name != '0' + if (test_powerlosses[p].run != run_powerloss_none && !(case_->flags & TEST_REENTRANT)) { continue; } @@ -646,7 +647,7 @@ static void case_forperm( } else { for (size_t p = 0; p < test_powerloss_count; p++) { // skip non-reentrant tests when powerloss testing - if (test_powerlosses[p].short_name != '0' + if (test_powerlosses[p].run != run_powerloss_none && !(case_->flags & TEST_REENTRANT)) { continue; } @@ -1094,7 +1095,7 @@ static void list_implicit_defines(void) { // make sure to include builtin geometries here extern const test_geometry_t builtin_geometries[]; - for (size_t g = 0; builtin_geometries[g].long_name; g++) { + for (size_t g = 0; builtin_geometries[g].name; g++) { test_define_geometry(&builtin_geometries[g]); test_define_flush(); @@ -1126,12 +1127,12 @@ static void list_implicit_defines(void) { // geometries to test const test_geometry_t builtin_geometries[] = { - {'d', "default", {{NULL}, TEST_CONST(16), TEST_CONST(512), {NULL}}}, - {'e', "eeprom", {{NULL}, TEST_CONST(1), TEST_CONST(512), {NULL}}}, - {'E', "emmc", {{NULL}, {NULL}, TEST_CONST(512), {NULL}}}, - {'n', "nor", {{NULL}, TEST_CONST(1), TEST_CONST(4096), {NULL}}}, - {'N', "nand", {{NULL}, TEST_CONST(4096), TEST_CONST(32768), {NULL}}}, - {0, NULL, {{NULL}, {NULL}, {NULL}, {NULL}}}, + {"default", {{NULL}, TEST_CONST(16), TEST_CONST(512), {NULL}}}, + {"eeprom", {{NULL}, TEST_CONST(1), TEST_CONST(512), {NULL}}}, + {"emmc", {{NULL}, {NULL}, TEST_CONST(512), {NULL}}}, + {"nor", {{NULL}, TEST_CONST(1), TEST_CONST(4096), {NULL}}}, + {"nand", {{NULL}, TEST_CONST(4096), TEST_CONST(32768), {NULL}}}, + {NULL, {{NULL}, {NULL}, {NULL}, {NULL}}}, }; const test_geometry_t *test_geometries = builtin_geometries; @@ -1144,12 +1145,11 @@ static void list_geometries(void) { printf("%-24s %7s %7s %7s %7s %11s\n", "geometry", "read", "prog", "erase", "count", "size"); - for (size_t g = 0; builtin_geometries[g].long_name; g++) { + for (size_t g = 0; builtin_geometries[g].name; g++) { test_define_geometry(&builtin_geometries[g]); test_define_flush(); - printf("%c,%-22s %7ju %7ju %7ju %7ju %11ju\n", - builtin_geometries[g].short_name, - builtin_geometries[g].long_name, + printf("%-24s %7ju %7ju %7ju %7ju %11ju\n", + builtin_geometries[g].name, READ_SIZE, PROG_SIZE, BLOCK_SIZE, @@ -1314,7 +1314,7 @@ static void run_powerloss_linear( } } -static void run_powerloss_exponential( +static void run_powerloss_log( const lfs_emubd_powercycles_t *cycles, size_t cycle_count, const struct test_suite *suite, @@ -1646,11 +1646,11 @@ static void run_powerloss_exhaustive( const test_powerloss_t builtin_powerlosses[] = { - {'0', "none", run_powerloss_none, NULL, 0}, - {'e', "exponential", run_powerloss_exponential, NULL, 0}, - {'l', "linear", run_powerloss_linear, NULL, 0}, - {'x', "exhaustive", run_powerloss_exhaustive, NULL, SIZE_MAX}, - {0, NULL, NULL, NULL, 0}, + {"none", run_powerloss_none, NULL, 0}, + {"log", run_powerloss_log, NULL, 0}, + {"linear", run_powerloss_linear, NULL, 0}, + {"exhaustive", run_powerloss_exhaustive, NULL, SIZE_MAX}, + {NULL, NULL, NULL, 0}, }; const char *const builtin_powerlosses_help[] = { @@ -1664,17 +1664,16 @@ const char *const builtin_powerlosses_help[] = { }; const test_powerloss_t *test_powerlosses = (const test_powerloss_t[]){ - {'0', "none", run_powerloss_none, NULL, 0}, + {"none", run_powerloss_none, NULL, 0}, }; size_t test_powerloss_count = 1; static void list_powerlosses(void) { printf("%-24s %s\n", "scenario", "description"); size_t i = 0; - for (; builtin_powerlosses[i].long_name; i++) { - printf("%c,%-22s %s\n", - builtin_powerlosses[i].short_name, - builtin_powerlosses[i].long_name, + for (; builtin_powerlosses[i].name; i++) { + printf("%-24s %s\n", + builtin_powerlosses[i].name, builtin_powerlosses_help[i]); } @@ -1765,8 +1764,8 @@ enum opt_flags { OPT_LIST_GEOMETRIES = 6, OPT_LIST_POWERLOSSES = 7, OPT_DEFINE = 'D', - OPT_GEOMETRY = 'g', - OPT_POWERLOSS = 'p', + OPT_GEOMETRY = 'G', + OPT_POWERLOSS = 'P', OPT_STEP = 's', OPT_DISK = 'd', OPT_TRACE = 't', @@ -1775,7 +1774,7 @@ enum opt_flags { OPT_ERASE_SLEEP = 10, }; -const char *short_opts = "hYlLD:g:p:s:d:t:"; +const char *short_opts = "hYlLD:G:P:s:d:t:"; const struct option long_opts[] = { {"help", no_argument, NULL, OPT_HELP}, @@ -1816,8 +1815,8 @@ const char *const help_text[] = { "List the available disk geometries.", "List the available power-loss scenarios.", "Override a test define.", - "Comma-separated list of disk geometries to test. Defaults to d,e,E,n,N.", - "Comma-separated list of power-loss scenarios to test. Defaults to 0,l.", + "Comma-separated list of disk geometries to test.", + "Comma-separated list of power-loss scenarios to test.", "Comma-separated range of test permutations to run (start,stop,step).", "Redirect block device operations to this file.", "Redirect trace output to this file.", @@ -2076,14 +2075,11 @@ invalid_define: // named disk geometry size_t len = strcspn(optarg, " ,"); - for (size_t i = 0; builtin_geometries[i].long_name; i++) { - if ((len == 1 - && *optarg == builtin_geometries[i].short_name) - || (len == strlen( - builtin_geometries[i].long_name) - && memcmp(optarg, - builtin_geometries[i].long_name, - len) == 0)) { + for (size_t i = 0; builtin_geometries[i].name; i++) { + if (len == strlen(builtin_geometries[i].name) + && memcmp(optarg, + builtin_geometries[i].name, + len) == 0) { *geometry = builtin_geometries[i]; optarg += len; goto geometry_next; @@ -2224,14 +2220,11 @@ geometry_next: // named power-loss scenario size_t len = strcspn(optarg, " ,"); - for (size_t i = 0; builtin_powerlosses[i].long_name; i++) { - if ((len == 1 - && *optarg == builtin_powerlosses[i].short_name) - || (len == strlen( - builtin_powerlosses[i].long_name) - && memcmp(optarg, - builtin_powerlosses[i].long_name, - len) == 0)) { + for (size_t i = 0; builtin_powerlosses[i].name; i++) { + if (len == strlen(builtin_powerlosses[i].name) + && memcmp(optarg, + builtin_powerlosses[i].name, + len) == 0) { *powerloss = builtin_powerlosses[i]; optarg += len; goto powerloss_next; diff --git a/scripts/bench.py b/scripts/bench.py index 93c18a2d..a59d80d3 100755 --- a/scripts/bench.py +++ b/scripts/bench.py @@ -511,7 +511,7 @@ def find_runner(runner, **args): # other context if args.get('geometry'): - cmd.append('-g%s' % args['geometry']) + cmd.append('-G%s' % args['geometry']) if args.get('disk'): cmd.append('-d%s' % args['disk']) if args.get('trace'): @@ -1003,6 +1003,10 @@ def run(runner, bench_ids=[], **args): total_perms)) print() + # automatic job detection? + if args.get('jobs') == 0: + args['jobs'] = len(os.sched_getaffinity(0)) + # truncate and open logs here so they aren't disconnected between benches stdout = None if args.get('stdout'): @@ -1246,9 +1250,8 @@ if __name__ == "__main__": action='append', help="Override a bench define.") bench_parser.add_argument( - '-g', '--geometry', - help="Comma-separated list of disk geometries to bench. " - "Defaults to d,e,E,n,N.") + '-G', '--geometry', + help="Comma-separated list of disk geometries to bench.") bench_parser.add_argument( '-d', '--disk', help="Direct block device operations to this file.") @@ -1274,8 +1277,8 @@ if __name__ == "__main__": '-j', '--jobs', nargs='?', type=lambda x: int(x, 0), - const=len(os.sched_getaffinity(0)), - help="Number of parallel runners to run.") + const=0, + help="Number of parallel runners to run. 0 runs one runner per core.") bench_parser.add_argument( '-k', '--keep-going', action='store_true', diff --git a/scripts/plot.py b/scripts/plot.py index ad59adca..9aef2aee 100755 --- a/scripts/plot.py +++ b/scripts/plot.py @@ -479,8 +479,8 @@ def main(csv_paths, *, x=None, y=None, define=[], - xlim=None, - ylim=None, + xlim=(None,None), + ylim=(None,None), width=None, height=17, cat=False, @@ -489,7 +489,7 @@ def main(csv_paths, *, colors=None, chars=None, line_chars=None, - no_lines=False, + points=False, legend=None, keep_open=False, sleep=None, @@ -503,9 +503,9 @@ def main(csv_paths, *, color = False # allow shortened ranges - if xlim is not None and len(xlim) == 1: + if len(xlim) == 1: xlim = (0, xlim[0]) - if ylim is not None and len(ylim) == 1: + if len(ylim) == 1: ylim = (0, ylim[0]) # separate out renames @@ -544,7 +544,7 @@ def main(csv_paths, *, if line_chars is not None: line_chars_ = line_chars - elif not no_lines: + elif not points: line_chars_ = [True] else: line_chars_ = [False] @@ -567,28 +567,26 @@ def main(csv_paths, *, legend_width = max(legend_width, len(label)+1) # find xlim/ylim - if xlim is not None: - xlim_ = xlim - else: - xlim_ = ( - min(it.chain([0], (k + xlim_ = ( + xlim[0] if xlim[0] is not None + else min(it.chain([0], (k for r in datasets_.values() for k, v in r.items() if v is not None))), - max(it.chain([0], (k + xlim[1] if xlim[1] is not None + else max(it.chain([0], (k for r in datasets_.values() for k, v in r.items() if v is not None)))) - if ylim is not None: - ylim_ = ylim - else: - ylim_ = ( - min(it.chain([0], (v + ylim_ = ( + ylim[0] if ylim[0] is not None + else min(it.chain([0], (v for r in datasets_.values() for _, v in r.items() if v is not None))), - max(it.chain([0], (v + ylim[1] if ylim[1] is not None + else max(it.chain([0], (v for r in datasets_.values() for _, v in r.items() if v is not None)))) @@ -740,17 +738,17 @@ if __name__ == "__main__": "or list of paths. Defaults to %r." % CSV_PATHS) parser.add_argument( '-b', '--by', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Fields to render as separate plots. All other fields will be " "summed as needed. Can rename fields with new_name=old_name.") parser.add_argument( '-x', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Fields to use for the x-axis. Can rename fields with " "new_name=old_name.") parser.add_argument( '-y', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Fields to use for the y-axis. Can rename fields with " "new_name=old_name.") parser.add_argument( @@ -771,7 +769,7 @@ if __name__ == "__main__": "sometimes suffer from inconsistent widths.") parser.add_argument( '--colors', - type=lambda x: x.split(','), + type=lambda x: [x.strip() for x in x.split(',')], help="Colors to use.") parser.add_argument( '--chars', @@ -780,17 +778,21 @@ if __name__ == "__main__": '--line-chars', help="Characters to use for lines.") parser.add_argument( - '-L', '--no-lines', + '-.', '--points', action='store_true', help="Only draw the data points.") parser.add_argument( '-W', '--width', + nargs='?', type=lambda x: int(x, 0), + const=0, help="Width in columns. 0 uses the terminal width. Defaults to " "min(terminal, 80).") parser.add_argument( '-H', '--height', + nargs='?', type=lambda x: int(x, 0), + const=0, help="Height in rows. 0 uses the terminal height. Defaults to 17.") parser.add_argument( '-z', '--cat', @@ -798,11 +800,15 @@ if __name__ == "__main__": help="Pipe directly to stdout.") parser.add_argument( '-X', '--xlim', - type=lambda x: tuple(dat(x) if x else None for x in x.split(',')), + type=lambda x: tuple( + dat(x) if x.strip() else None + for x in x.split(',')), help="Range for the x-axis.") parser.add_argument( '-Y', '--ylim', - type=lambda x: tuple(dat(x) if x else None for x in x.split(',')), + type=lambda x: tuple( + dat(x) if x.strip() else None + for x in x.split(',')), help="Range for the y-axis.") parser.add_argument( '--xlog', diff --git a/scripts/summary.py b/scripts/summary.py index 36b556f5..60bf948e 100755 --- a/scripts/summary.py +++ b/scripts/summary.py @@ -671,46 +671,46 @@ if __name__ == "__main__": help="Only show percentage change, not a full diff.") parser.add_argument( '-b', '--by', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Group by these fields. All other fields will be merged as " "needed. Can rename fields with new_name=old_name.") parser.add_argument( '-f', '--fields', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Use these fields. Can rename fields with new_name=old_name.") parser.add_argument( '-D', '--define', - type=lambda x: (lambda k,v: (k, set(v.split(','))))(*x.split('=', 1)), action='append', + type=lambda x: (lambda k,v: (k, set(v.split(','))))(*x.split('=', 1)), help="Only include rows where this field is this value. May include " "comma-separated options.") parser.add_argument( '--add', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Add these fields (the default).") parser.add_argument( '--mul', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Multiply these fields.") parser.add_argument( '--min', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Take the minimum of these fields.") parser.add_argument( '--max', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Take the maximum of these fields.") parser.add_argument( '--avg', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Average these fields.") parser.add_argument( '-s', '--sort', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Sort by these fields.") parser.add_argument( '-S', '--reverse-sort', - type=lambda x: [x.strip() for x in x.split(',')], + action='append', help="Sort by these fields, but backwards.") parser.add_argument( '-Y', '--summary', diff --git a/scripts/tailpipe.py b/scripts/tailpipe.py index 3db7612f..7e8c4543 100755 --- a/scripts/tailpipe.py +++ b/scripts/tailpipe.py @@ -121,9 +121,10 @@ if __name__ == "__main__": nargs='?', help="Path to read from.") parser.add_argument( - '-n', - '--lines', + '-n', '--lines', + nargs='?', type=lambda x: int(x, 0), + const=0, help="Show this many lines of history. 0 uses the terminal height. " "Defaults to 5.") parser.add_argument( diff --git a/scripts/test.py b/scripts/test.py index 71688547..eaab3e38 100755 --- a/scripts/test.py +++ b/scripts/test.py @@ -525,9 +525,9 @@ def find_runner(runner, **args): # other context if args.get('geometry'): - cmd.append('-g%s' % args['geometry']) + cmd.append('-G%s' % args['geometry']) if args.get('powerloss'): - cmd.append('-p%s' % args['powerloss']) + cmd.append('-P%s' % args['powerloss']) if args.get('disk'): cmd.append('-d%s' % args['disk']) if args.get('trace'): @@ -1009,6 +1009,10 @@ def run(runner, test_ids=[], **args): total_perms)) print() + # automatic job detection? + if args.get('jobs') == 0: + args['jobs'] = len(os.sched_getaffinity(0)) + # truncate and open logs here so they aren't disconnected between tests stdout = None if args.get('stdout'): @@ -1251,13 +1255,11 @@ if __name__ == "__main__": action='append', help="Override a test define.") test_parser.add_argument( - '-g', '--geometry', - help="Comma-separated list of disk geometries to test. " - "Defaults to d,e,E,n,N.") + '-G', '--geometry', + help="Comma-separated list of disk geometries to test.") test_parser.add_argument( - '-p', '--powerloss', - help="Comma-separated list of power-loss scenarios to test. " - "Defaults to 0,l.") + '-P', '--powerloss', + help="Comma-separated list of power-loss scenarios to test.") test_parser.add_argument( '-d', '--disk', help="Direct block device operations to this file.") @@ -1283,8 +1285,8 @@ if __name__ == "__main__": '-j', '--jobs', nargs='?', type=lambda x: int(x, 0), - const=len(os.sched_getaffinity(0)), - help="Number of parallel runners to run.") + const=0, + help="Number of parallel runners to run. 0 runs one runner per core.") test_parser.add_argument( '-k', '--keep-going', action='store_true', diff --git a/scripts/tracebd.py b/scripts/tracebd.py index 84b0b29a..d69c1329 100755 --- a/scripts/tracebd.py +++ b/scripts/tracebd.py @@ -853,11 +853,15 @@ if __name__ == "__main__": help="Render wear.") parser.add_argument( '-b', '--block', - type=lambda x: tuple(int(x,0) if x else None for x in x.split(',',1)), + type=lambda x: tuple( + int(x, 0) if x.strip() else None + for x in x.split(',')), help="Show a specific block or range of blocks.") parser.add_argument( '-i', '--off', - type=lambda x: tuple(int(x,0) if x else None for x in x.split(',',1)), + type=lambda x: tuple( + int(x, 0) if x.strip() else None + for x in x.split(',')), help="Show a specific offset or range of offsets.") parser.add_argument( '-B', '--block-size', @@ -901,24 +905,30 @@ if __name__ == "__main__": help="Characters to use for showing wear.") parser.add_argument( '--colors', - type=lambda x: x.split(','), + type=lambda x: [x.strip() for x in x.split(',')], help="Colors to use for read, prog, erase, noop operations.") parser.add_argument( '--wear-colors', - type=lambda x: x.split(','), + type=lambda x: [x.strip() for x in x.split(',')], help="Colors to use for showing wear.") parser.add_argument( '-W', '--width', + nargs='?', type=lambda x: int(x, 0), + const=0, help="Width in columns. 0 uses the terminal width. Defaults to " "min(terminal, 80).") parser.add_argument( '-H', '--height', + nargs='?', type=lambda x: int(x, 0), + const=0, help="Height in rows. 0 uses the terminal height. Defaults to 1.") parser.add_argument( '-n', '--lines', + nargs='?', type=lambda x: int(x, 0), + const=0, help="Show this many lines of history. 0 uses the terminal height. " "Defaults to 5.") parser.add_argument(