)
+@dataclass(frozen=True, kw_only=True)
+class Pickletools(ThemeSection):
+ annotation: str = ANSIColors.GREY
+ arg_number: str = ANSIColors.YELLOW
+ arg_string: str = ANSIColors.GREEN
+ mark: str = ANSIColors.GREY
+ op_call: str = ANSIColors.GREEN
+ op_container: str = ANSIColors.INTENSE_BLUE
+ op_memo: str = ANSIColors.MAGENTA
+ op_meta: str = ANSIColors.GREY
+ op_stack: str = ANSIColors.BOLD_RED
+ opcode_code: str = ANSIColors.CYAN
+ position: str = ANSIColors.GREY
+ proto: str = ANSIColors.YELLOW
+ reset: str = ANSIColors.RESET
+
+
@dataclass(frozen=True, kw_only=True)
class Syntax(ThemeSection):
prompt: str = ANSIColors.BOLD_MAGENTA
fancycompleter: FancyCompleter = field(default_factory=FancyCompleter)
http_server: HttpServer = field(default_factory=HttpServer)
live_profiler: LiveProfiler = field(default_factory=LiveProfiler)
+ pickletools: Pickletools = field(default_factory=Pickletools)
syntax: Syntax = field(default_factory=Syntax)
timeit: Timeit = field(default_factory=Timeit)
tokenize: Tokenize = field(default_factory=Tokenize)
fancycompleter: FancyCompleter | None = None,
http_server: HttpServer | None = None,
live_profiler: LiveProfiler | None = None,
+ pickletools: Pickletools | None = None,
syntax: Syntax | None = None,
timeit: Timeit | None = None,
tokenize: Tokenize | None = None,
fancycompleter=fancycompleter or self.fancycompleter,
http_server=http_server or self.http_server,
live_profiler=live_profiler or self.live_profiler,
+ pickletools=pickletools or self.pickletools,
syntax=syntax or self.syntax,
timeit=timeit or self.timeit,
tokenize=tokenize or self.tokenize,
fancycompleter=FancyCompleter.no_colors(),
http_server=HttpServer.no_colors(),
live_profiler=LiveProfiler.no_colors(),
+ pickletools=Pickletools.no_colors(),
syntax=Syntax.no_colors(),
timeit=Timeit.no_colors(),
tokenize=Tokenize.no_colors(),
import re
import sys
+lazy from _colorize import decolor, get_theme
+
__all__ = ['dis', 'genops', 'optimize']
bytes_types = pickle.bytes_types
name2i[d.name] = i
code2i[d.code] = i
+# Group opcode names into categories for colourised CLI output.
+_opcode_categories = frozendict(
+ op_call=frozenset({
+ "BUILD", "EXT1", "EXT2", "EXT4", "GLOBAL", "INST", "NEWOBJ",
+ "NEWOBJ_EX", "OBJ", "REDUCE", "STACK_GLOBAL",
+ }),
+ op_container=frozenset({
+ "ADDITEMS", "APPEND", "APPENDS", "DICT", "EMPTY_DICT", "EMPTY_LIST",
+ "EMPTY_SET", "EMPTY_TUPLE", "FROZENSET", "LIST", "SETITEM",
+ "SETITEMS", "TUPLE", "TUPLE1", "TUPLE2", "TUPLE3",
+ }),
+ op_memo=frozenset({
+ "BINGET", "BINPUT", "GET", "LONG_BINGET", "LONG_BINPUT", "MEMOIZE",
+ "PUT",
+ }),
+ op_meta=frozenset({"BINPERSID", "FRAME", "MARK", "PERSID", "PROTO"}),
+ op_stack=frozenset({"DUP", "POP", "POP_MARK", "STOP"}),
+)
+_opcode_color_attr = frozendict({
+ name: attr
+ for attr, names in _opcode_categories.items()
+ for name in names
+})
+assert _opcode_color_attr.keys() <= name2i.keys(), (
+ f"unknown opcodes: {_opcode_color_attr.keys() - name2i.keys()}"
+)
del name2i, code2i, i, d
##############################################################################
indentchunk = ' ' * indentlevel
errormsg = None
annocol = annotate # column hint for annotations
+ t = get_theme(tty_file=out).pickletools
for opcode, arg, pos in genops(pickle):
if pos is not None:
- print("%5d:" % pos, end=' ', file=out)
+ print(f"{t.position}{pos:5d}:{t.reset}", end=' ', file=out)
- line = "%-4s %s%s" % (repr(opcode.code)[1:-1],
- indentchunk * len(markstack),
- opcode.name)
+ attr = _opcode_color_attr.get(opcode.name)
+ opcode_color = getattr(t, attr) if attr else ""
+ opcode_reset = t.reset if attr else ""
+ line = (
+ f"{t.opcode_code}{repr(opcode.code)[1:-1]:<4}{t.reset} "
+ f"{indentchunk * len(markstack)}"
+ f"{opcode_color}{opcode.name}{opcode_reset}"
+ )
maxproto = max(maxproto, opcode.proto)
before = opcode.stack_before # don't mutate
line += ' ' * (10 - len(opcode.name))
if arg is not None:
if opcode.name in ("STRING", "BINSTRING", "SHORT_BINSTRING"):
- line += ' ' + ascii(arg)
+ arg_text = ascii(arg)
else:
- line += ' ' + repr(arg)
+ arg_text = repr(arg)
+ arg_color = (
+ t.arg_number
+ if isinstance(arg, (int, float))
+ else t.arg_string
+ )
+ line += f" {arg_color}{arg_text}{t.reset}"
if markmsg:
- line += ' ' + markmsg
+ line += f" {t.mark}{markmsg}{t.reset}"
if annotate:
- line += ' ' * (annocol - len(line))
+ visible_len = len(decolor(line))
+ line += ' ' * (annocol - visible_len)
# make a mild effort to align annotations
- annocol = len(line)
+ annocol = max(visible_len, annocol)
if annocol > 50:
annocol = annotate
- line += ' ' + opcode.doc.split('\n', 1)[0]
+ doc = opcode.doc.split('\n', 1)[0]
+ line += f" {t.annotation}{doc}{t.reset}"
print(line, file=out)
if errormsg:
stack.extend(after)
- print("highest protocol among opcodes =", maxproto, file=out)
+ print(
+ "highest protocol among opcodes =",
+ f"{t.proto}{maxproto}{t.reset}",
+ file=out,
+ )
if stack:
raise ValueError("stack not empty after STOP: %r" % stack)
def _main(args=None):
import argparse
- parser = argparse.ArgumentParser(
- description='disassemble one or more pickle files',
- color=True,
- )
+ parser = argparse.ArgumentParser(description='disassemble one or more pickle files')
parser.add_argument(
'pickle_file',
nargs='+', help='the pickle file')