| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -79,6 +79,9 @@ Command-line options | |||
| 79 | 79 | ||
| 80 | 80 | A pickle file to read, or ``-`` to indicate reading from standard input. | |
| 81 | 81 | ||
| 82 | + .. versionadded:: next | ||
| 83 | + Output is in color by default and can be | ||
| 84 | + :ref:`controlled using environment variables <using-on-controlling-color>`. | ||
| 82 | 85 | ||
| 83 | 86 | ||
| 84 | 87 | Programmatic interface | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1053,6 +1053,15 @@ pickle | |||
| 1053 | 1053 | (Contributed by Zackery Spytz and Serhiy Storchaka in :gh:`77188`.) | |
| 1054 | 1054 | ||
| 1055 | 1055 | ||
| 1056 | + pickletools | ||
| 1057 | + ----------- | ||
| 1058 | + | ||
| 1059 | + * The output of the :mod:`pickletools` command-line interface is colored by | ||
| 1060 | + default. This can be controlled with | ||
| 1061 | + :ref:`environment variables <using-on-controlling-color>`. | ||
| 1062 | + (Contributed by Hugo van Kemenade in :gh:`149026`.) | ||
| 1063 | + | ||
| 1064 | + | ||
| 1056 | 1065 | pprint | |
| 1057 | 1066 | ------ | |
| 1058 | 1067 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -359,6 +359,23 @@ class LiveProfiler(ThemeSection): | |||
| 359 | 359 | ) | |
| 360 | 360 | ||
| 361 | 361 | ||
| 362 | + @dataclass(frozen=True, kw_only=True) | ||
| 363 | + class Pickletools(ThemeSection): | ||
| 364 | + annotation: str = ANSIColors.GREY | ||
| 365 | + arg_number: str = ANSIColors.YELLOW | ||
| 366 | + arg_string: str = ANSIColors.GREEN | ||
| 367 | + mark: str = ANSIColors.GREY | ||
| 368 | + op_call: str = ANSIColors.GREEN | ||
| 369 | + op_container: str = ANSIColors.INTENSE_BLUE | ||
| 370 | + op_memo: str = ANSIColors.MAGENTA | ||
| 371 | + op_meta: str = ANSIColors.GREY | ||
| 372 | + op_stack: str = ANSIColors.BOLD_RED | ||
| 373 | + opcode_code: str = ANSIColors.CYAN | ||
| 374 | + position: str = ANSIColors.GREY | ||
| 375 | + proto: str = ANSIColors.YELLOW | ||
| 376 | + reset: str = ANSIColors.RESET | ||
| 377 | + | ||
| 378 | + | ||
| 362 | 379 | @dataclass(frozen=True, kw_only=True) | |
| 363 | 380 | class Syntax(ThemeSection): | |
| 364 | 381 | prompt: str = ANSIColors.BOLD_MAGENTA | |
@@ -429,6 +446,7 @@ class Theme: | |||
| 429 | 446 | fancycompleter: FancyCompleter = field(default_factory=FancyCompleter) | |
| 430 | 447 | http_server: HttpServer = field(default_factory=HttpServer) | |
| 431 | 448 | live_profiler: LiveProfiler = field(default_factory=LiveProfiler) | |
| 449 | + pickletools: Pickletools = field(default_factory=Pickletools) | ||
| 432 | 450 | syntax: Syntax = field(default_factory=Syntax) | |
| 433 | 451 | timeit: Timeit = field(default_factory=Timeit) | |
| 434 | 452 | tokenize: Tokenize = field(default_factory=Tokenize) | |
@@ -444,6 +462,7 @@ def copy_with( | |||
| 444 | 462 | fancycompleter: FancyCompleter | None = None, | |
| 445 | 463 | http_server: HttpServer | None = None, | |
| 446 | 464 | live_profiler: LiveProfiler | None = None, | |
| 465 | + pickletools: Pickletools | None = None, | ||
| 447 | 466 | syntax: Syntax | None = None, | |
| 448 | 467 | timeit: Timeit | None = None, | |
| 449 | 468 | tokenize: Tokenize | None = None, | |
@@ -462,6 +481,7 @@ def copy_with( | |||
| 462 | 481 | fancycompleter=fancycompleter or self.fancycompleter, | |
| 463 | 482 | http_server=http_server or self.http_server, | |
| 464 | 483 | live_profiler=live_profiler or self.live_profiler, | |
| 484 | + pickletools=pickletools or self.pickletools, | ||
| 465 | 485 | syntax=syntax or self.syntax, | |
| 466 | 486 | timeit=timeit or self.timeit, | |
| 467 | 487 | tokenize=tokenize or self.tokenize, | |
@@ -484,6 +504,7 @@ def no_colors(cls) -> Self: | |||
| 484 | 504 | fancycompleter=FancyCompleter.no_colors(), | |
| 485 | 505 | http_server=HttpServer.no_colors(), | |
| 486 | 506 | live_profiler=LiveProfiler.no_colors(), | |
| 507 | + pickletools=Pickletools.no_colors(), | ||
| 487 | 508 | syntax=Syntax.no_colors(), | |
| 488 | 509 | timeit=Timeit.no_colors(), | |
| 489 | 510 | tokenize=Tokenize.no_colors(), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,6 +16,8 @@ | |||
| 16 | 16 | import re | |
| 17 | 17 | import sys | |
| 18 | 18 | ||
| 19 | + lazy from _colorize import decolor, get_theme | ||
| 20 | + | ||
| 19 | 21 | __all__ = ['dis', 'genops', 'optimize'] | |
| 20 | 22 | ||
| 21 | 23 | bytes_types = pickle.bytes_types | |
@@ -2209,6 +2211,32 @@ def __init__(self, name, code, arg, | |||
| 2209 | 2211 | name2i[d.name] = i | |
| 2210 | 2212 | code2i[d.code] = i | |
| 2211 | 2213 | ||
| 2214 | + # Group opcode names into categories for colourised CLI output. | ||
| 2215 | + _opcode_categories = frozendict( | ||
| 2216 | + op_call=frozenset({ | ||
| 2217 | + "BUILD", "EXT1", "EXT2", "EXT4", "GLOBAL", "INST", "NEWOBJ", | ||
| 2218 | + "NEWOBJ_EX", "OBJ", "REDUCE", "STACK_GLOBAL", | ||
| 2219 | + }), | ||
| 2220 | + op_container=frozenset({ | ||
| 2221 | + "ADDITEMS", "APPEND", "APPENDS", "DICT", "EMPTY_DICT", "EMPTY_LIST", | ||
| 2222 | + "EMPTY_SET", "EMPTY_TUPLE", "FROZENSET", "LIST", "SETITEM", | ||
| 2223 | + "SETITEMS", "TUPLE", "TUPLE1", "TUPLE2", "TUPLE3", | ||
| 2224 | + }), | ||
| 2225 | + op_memo=frozenset({ | ||
| 2226 | + "BINGET", "BINPUT", "GET", "LONG_BINGET", "LONG_BINPUT", "MEMOIZE", | ||
| 2227 | + "PUT", | ||
| 2228 | + }), | ||
| 2229 | + op_meta=frozenset({"BINPERSID", "FRAME", "MARK", "PERSID", "PROTO"}), | ||
| 2230 | + op_stack=frozenset({"DUP", "POP", "POP_MARK", "STOP"}), | ||
| 2231 | + ) | ||
| 2232 | + _opcode_color_attr = frozendict({ | ||
| 2233 | + name: attr | ||
| 2234 | + for attr, names in _opcode_categories.items() | ||
| 2235 | + for name in names | ||
| 2236 | + }) | ||
| 2237 | + assert _opcode_color_attr.keys() <= name2i.keys(), ( | ||
| 2238 | + f"unknown opcodes: {_opcode_color_attr.keys() - name2i.keys()}" | ||
| 2239 | + ) | ||
| 2212 | 2240 | del name2i, code2i, i, d | |
| 2213 | 2241 | ||
| 2214 | 2242 | ############################################################################## | |
@@ -2443,13 +2471,19 @@ def dis(pickle, out=None, memo=None, indentlevel=4, annotate=0): | |||
| 2443 | 2471 | indentchunk = ' ' * indentlevel | |
| 2444 | 2472 | errormsg = None | |
| 2445 | 2473 | annocol = annotate # column hint for annotations | |
| 2474 | + t = get_theme(tty_file=out).pickletools | ||
| 2446 | 2475 | for opcode, arg, pos in genops(pickle): | |
| 2447 | 2476 | if pos is not None: | |
| 2448 | - print("%5d:" % pos, end=' ', file=out) | ||
| 2477 | + print(f"{t.position}{pos:5d}:{t.reset}", end=' ', file=out) | ||
| 2449 | 2478 | ||
| 2450 | - line = "%-4s %s%s" % (repr(opcode.code)[1:-1], | ||
| 2451 | - indentchunk * len(markstack), | ||
| 2452 | - opcode.name) | ||
| 2479 | + attr = _opcode_color_attr.get(opcode.name) | ||
| 2480 | + opcode_color = getattr(t, attr) if attr else "" | ||
| 2481 | + opcode_reset = t.reset if attr else "" | ||
| 2482 | + line = ( | ||
| 2483 | + f"{t.opcode_code}{repr(opcode.code)[1:-1]:<4}{t.reset} " | ||
| 2484 | + f"{indentchunk * len(markstack)}" | ||
| 2485 | + f"{opcode_color}{opcode.name}{opcode_reset}" | ||
| 2486 | + ) | ||
| 2453 | 2487 | ||
| 2454 | 2488 | maxproto = max(maxproto, opcode.proto) | |
| 2455 | 2489 | before = opcode.stack_before # don't mutate | |
@@ -2510,18 +2544,26 @@ def dis(pickle, out=None, memo=None, indentlevel=4, annotate=0): | |||
| 2510 | 2544 | line += ' ' * (10 - len(opcode.name)) | |
| 2511 | 2545 | if arg is not None: | |
| 2512 | 2546 | if opcode.name in ("STRING", "BINSTRING", "SHORT_BINSTRING"): | |
| 2513 | - line += ' ' + ascii(arg) | ||
| 2547 | + arg_text = ascii(arg) | ||
| 2514 | 2548 | else: | |
| 2515 | - line += ' ' + repr(arg) | ||
| 2549 | + arg_text = repr(arg) | ||
| 2550 | + arg_color = ( | ||
| 2551 | + t.arg_number | ||
| 2552 | + if isinstance(arg, (int, float)) | ||
| 2553 | + else t.arg_string | ||
| 2554 | + ) | ||
| 2555 | + line += f" {arg_color}{arg_text}{t.reset}" | ||
| 2516 | 2556 | if markmsg: | |
| 2517 | - line += ' ' + markmsg | ||
| 2557 | + line += f" {t.mark}{markmsg}{t.reset}" | ||
| 2518 | 2558 | if annotate: | |
| 2519 | - line += ' ' * (annocol - len(line)) | ||
| 2559 | + visible_len = len(decolor(line)) | ||
| 2560 | + line += ' ' * (annocol - visible_len) | ||
| 2520 | 2561 | # make a mild effort to align annotations | |
| 2521 | - annocol = len(line) | ||
| 2562 | + annocol = max(visible_len, annocol) | ||
| 2522 | 2563 | if annocol > 50: | |
| 2523 | 2564 | annocol = annotate | |
| 2524 | - line += ' ' + opcode.doc.split('\n', 1)[0] | ||
| 2565 | + doc = opcode.doc.split('\n', 1)[0] | ||
| 2566 | + line += f" {t.annotation}{doc}{t.reset}" | ||
| 2525 | 2567 | print(line, file=out) | |
| 2526 | 2568 | ||
| 2527 | 2569 | if errormsg: | |
@@ -2541,7 +2583,11 @@ def dis(pickle, out=None, memo=None, indentlevel=4, annotate=0): | |||
| 2541 | 2583 | ||
| 2542 | 2584 | stack.extend(after) | |
| 2543 | 2585 | ||
| 2544 | - print("highest protocol among opcodes =", maxproto, file=out) | ||
| 2586 | + print( | ||
| 2587 | + "highest protocol among opcodes =", | ||
| 2588 | + f"{t.proto}{maxproto}{t.reset}", | ||
| 2589 | + file=out, | ||
| 2590 | + ) | ||
| 2545 | 2591 | if stack: | |
| 2546 | 2592 | raise ValueError("stack not empty after STOP: %r" % stack) | |
| 2547 | 2593 | ||
@@ -2841,10 +2887,7 @@ def __init__(self, value): | |||
| 2841 | 2887 | ||
| 2842 | 2888 | def _main(args=None): | |
| 2843 | 2889 | import argparse | |
| 2844 | - parser = argparse.ArgumentParser( | ||
| 2845 | - description='disassemble one or more pickle files', | ||
| 2846 | - color=True, | ||
| 2847 | - ) | ||
| 2890 | + parser = argparse.ArgumentParser(description='disassemble one or more pickle files') | ||
| 2848 | 2891 | parser.add_argument( | |
| 2849 | 2892 | 'pickle_file', | |
| 2850 | 2893 | nargs='+', help='the pickle file') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -160,6 +160,7 @@ def test_unknown_opcode_without_pos(self): | |||
| 160 | 160 | next(it) | |
| 161 | 161 | ||
| 162 | 162 | ||
| 163 | + @support.force_not_colorized_test_class | ||
| 163 | 164 | class DisTests(unittest.TestCase): | |
| 164 | 165 | maxDiff = None | |
| 165 | 166 | ||
@@ -518,6 +519,7 @@ def test__all__(self): | |||
| 518 | 519 | support.check__all__(self, pickletools, not_exported=not_exported) | |
| 519 | 520 | ||
| 520 | 521 | ||
| 522 | + @support.force_not_colorized_test_class | ||
| 521 | 523 | class CommandLineTest(unittest.TestCase): | |
| 522 | 524 | def setUp(self): | |
| 523 | 525 | self.filename = tempfile.mktemp() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + Add colour to :mod:`pickletools` CLI output. Patch by Hugo van Kemenade. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments