From: Sergey Matveev Date: Tue, 13 Feb 2018 18:03:42 +0000 (+0300) Subject: Colourized output X-Git-Tag: 3.0~1 X-Git-Url: http://www.git.cypherpunks.ru/?p=pyderasn.git;a=commitdiff_plain;h=3bdec8f765e0fa8ed27162a8c0abe391a44dc664 Colourized output --- diff --git a/VERSION b/VERSION index 879b416..9f55b2c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1 +3.0 diff --git a/doc/index.rst b/doc/index.rst index 2eb228a..0197abd 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -19,6 +19,12 @@ README), it is still often can be seen anywhere in our life. PyDERASN is `free software `__, licenced under `GNU LGPLv3+ `__. +.. figure:: pprinting.png + :alt: Pretty printing example output + + An example of pretty printed X.509 certificate with automatically + parsed DEFINED BY fields. + .. toctree:: :maxdepth: 1 diff --git a/doc/install.rst b/doc/install.rst index d1e1325..e6f6ba1 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -4,11 +4,11 @@ Install Preferable way is to :ref:`download ` tarball with the signature from `official website `__:: - % wget http://pyderasn.cypherpunks.ru/pyderasn-1.0.tar.xz - % wget http://pyderasn.cypherpunks.ru/pyderasn-1.0.tar.xz.sig - % gpg --verify pyderasn-1.0.tar.xz.sig pyderasn-1.0.tar.xz - % xz -d < pyderasn-1.0.tar.xz | tar xf - - % cd pyderasn-1.0 + % wget http://pyderasn.cypherpunks.ru/pyderasn-3.0.tar.xz + % wget http://pyderasn.cypherpunks.ru/pyderasn-3.0.tar.xz.sig + % gpg --verify pyderasn-3.0.tar.xz.sig pyderasn-3.0.tar.xz + % xz -d < pyderasn-3.0.tar.xz | tar xf - + % cd pyderasn-3.0 % python setup.py install # or copy pyderasn.py (+six.py) to your PYTHONPATH diff --git a/doc/news.rst b/doc/news.rst index e935e0e..ab752f4 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -1,6 +1,15 @@ News ==== +.. _release3.0: + +3.0 +--- +* :py:func:`pyderasn.decode_path_defby` is replaced with + :py:class:`pyderasn.DecodePathDefBy` +* Ability to turn colourized terminal output by calling + ``pprint(..., with_colours=True)`` + .. _release2.1: 2.1 diff --git a/doc/pprinting.png b/doc/pprinting.png new file mode 100644 index 0000000..8f74110 Binary files /dev/null and b/doc/pprinting.png differ diff --git a/pyderasn.py b/pyderasn.py index cd96a33..e69b190 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -329,7 +329,7 @@ of ``PKIResponse``:: ( ( "content", - decode_path_defby(id_signedData), + DecodePathDefBy(id_signedData), "encapContentInfo", "eContentType", ), @@ -341,10 +341,10 @@ of ``PKIResponse``:: ( ( "content", - decode_path_defby(id_signedData), + DecodePathDefBy(id_signedData), "encapContentInfo", "eContent", - decode_path_defby(id_cct_PKIResponse), + DecodePathDefBy(id_cct_PKIResponse), "controlSequence", any, "attrType", @@ -358,7 +358,7 @@ of ``PKIResponse``:: ), )) -Pay attention for :py:func:`pyderasn.decode_path_defby` and ``any``. +Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``. First function is useful for path construction when some automatic decoding is already done. ``any`` means literally any value it meet -- useful for SEQUENCE/SET OF-s. @@ -486,6 +486,13 @@ from six import text_type from six.moves import xrange as six_xrange +try: + from termcolor import colored +except ImportError: + def colored(what, *args): + return what + + __all__ = ( "Any", "BitString", @@ -493,8 +500,8 @@ __all__ = ( "Boolean", "BoundsError", "Choice", - "decode_path_defby", "DecodeError", + "DecodePathDefBy", "Enumerated", "GeneralizedTime", "GeneralString", @@ -1001,10 +1008,24 @@ class Obj(object): return self.expl_tlen + self.expl_llen + self.expl_vlen -def decode_path_defby(defined_by): +class DecodePathDefBy(object): """DEFINED BY representation inside decode path """ - return "DEFINED BY (%s)" % defined_by + __slots__ = ('defined_by',) + + def __init__(self, defined_by): + self.defined_by = defined_by + + def __eq__(self, their): + if not isinstance(their, self.__class__): + return False + return self.defined_by == their.defined_by + + def __str__(self): + return "DEFINED BY " + str(self.defined_by) + + def __repr__(self): + return "<%s: %s>" % (self.__class__.__name__, self.defined_by) ######################################################################## @@ -1072,49 +1093,75 @@ def _pp( ) -def pp_console_row(pp, oids=None, with_offsets=False, with_blob=True): +def _colorize(what, colour, with_colours, attrs=("bold",)): + return colored(what, colour, attrs=attrs) if with_colours else what + + +def pp_console_row( + pp, + oids=None, + with_offsets=False, + with_blob=True, + with_colours=False, +): cols = [] if with_offsets: - cols.append("%5d%s [%d,%d,%4d]" % ( + col = "%5d%s" % ( pp.offset, ( " " if pp.expl_offset is None else ("-%d" % (pp.offset - pp.expl_offset)) ), - pp.tlen, - pp.llen, - pp.vlen, - )) + ) + cols.append(_colorize(col, "red", with_colours, ())) + col = "[%d,%d,%4d]" % (pp.tlen, pp.llen, pp.vlen) + cols.append(_colorize(col, "green", with_colours, ())) if len(pp.decode_path) > 0: cols.append(" ." * (len(pp.decode_path))) - cols.append("%s:" % pp.decode_path[-1]) + ent = pp.decode_path[-1] + if isinstance(ent, DecodePathDefBy): + cols.append(_colorize("DEFINED BY", "red", with_colours, ("reverse",))) + value = str(ent.defined_by) + if ( + oids is not None and + ent.defined_by.asn1_type_name == + ObjectIdentifier.asn1_type_name and + value in oids + ): + cols.append(_colorize("%s:" % oids[value], "green", with_colours)) + else: + cols.append(_colorize("%s:" % value, "white", with_colours)) + else: + cols.append(_colorize("%s:" % ent, "yellow", with_colours)) if pp.expl is not None: klass, _, num = pp.expl - cols.append("[%s%d] EXPLICIT" % (TagClassReprs[klass], num)) + col = "[%s%d] EXPLICIT" % (TagClassReprs[klass], num) + cols.append(_colorize(col, "blue", with_colours)) if pp.impl is not None: klass, _, num = pp.impl - cols.append("[%s%d]" % (TagClassReprs[klass], num)) + col = "[%s%d]" % (TagClassReprs[klass], num) + cols.append(_colorize(col, "blue", with_colours)) if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper(): - cols.append(pp.obj_name) - cols.append(pp.asn1_type_name) + cols.append(_colorize(pp.obj_name, "magenta", with_colours)) + cols.append(_colorize(pp.asn1_type_name, "cyan", with_colours)) if pp.value is not None: value = pp.value + cols.append(_colorize(value, "white", with_colours)) if ( oids is not None and pp.asn1_type_name == ObjectIdentifier.asn1_type_name and value in oids ): - value = "%s (%s)" % (oids[value], pp.value) - cols.append(value) + cols.append(_colorize("(%s)" % oids[value], "green", with_colours)) if with_blob: if isinstance(pp.blob, binary_type): cols.append(hexenc(pp.blob)) elif isinstance(pp.blob, tuple): cols.append(", ".join(pp.blob)) if pp.optional: - cols.append("OPTIONAL") + cols.append(_colorize("OPTIONAL", "red", with_colours)) if pp.default: - cols.append("DEFAULT") + cols.append(_colorize("DEFAULT", "red", with_colours)) return " ".join(cols) @@ -1133,7 +1180,7 @@ def pp_console_blob(pp): yield " ".join(cols + [", ".join(pp.blob)]) -def pprint(obj, oids=None, big_blobs=False): +def pprint(obj, oids=None, big_blobs=False, with_colours=False): """Pretty print object :param Obj obj: object you want to pretty print @@ -1142,6 +1189,8 @@ def pprint(obj, oids=None, big_blobs=False): :param big_blobs: if large binary objects are met (like OctetString values), do we need to print them too, on separate lines + :param with_colours: colourize output, if ``termcolor`` library + is available """ def _pprint_pps(pps): for pp in pps: @@ -1152,11 +1201,18 @@ def pprint(obj, oids=None, big_blobs=False): oids=oids, with_offsets=True, with_blob=False, + with_colours=with_colours, ) for row in pp_console_blob(pp): yield row else: - yield pp_console_row(pp, oids=oids, with_offsets=True) + yield pp_console_row( + pp, + oids=oids, + with_offsets=True, + with_blob=True, + with_colours=with_colours, + ) else: for row in _pprint_pps(pp): yield row @@ -2009,7 +2065,7 @@ class BitString(Obj): defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( - decode_path=decode_path + (decode_path_defby(defined_by),) + decode_path=decode_path + (DecodePathDefBy(defined_by),) ) @@ -2233,7 +2289,7 @@ class OctetString(Obj): defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( - decode_path=decode_path + (decode_path_defby(defined_by),) + decode_path=decode_path + (DecodePathDefBy(defined_by),) ) @@ -3545,7 +3601,7 @@ class Any(Obj): defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( - decode_path=decode_path + (decode_path_defby(defined_by),) + decode_path=decode_path + (DecodePathDefBy(defined_by),) ) @@ -3871,7 +3927,7 @@ class Sequence(Obj): for i, _value in enumerate(value): sub_sub_decode_path = sub_decode_path + ( str(i), - decode_path_defby(defined_by), + DecodePathDefBy(defined_by), ) defined_value, defined_tail = defined_spec.decode( memoryview(bytes(_value)), @@ -3899,14 +3955,14 @@ class Sequence(Obj): if value.expled else (value.tlen + value.llen) ), leavemm=True, - decode_path=sub_decode_path + (decode_path_defby(defined_by),), + decode_path=sub_decode_path + (DecodePathDefBy(defined_by),), ctx=ctx, ) if len(defined_tail) > 0: raise DecodeError( "remaining data", klass=self.__class__, - decode_path=sub_decode_path + (decode_path_defby(defined_by),), + decode_path=sub_decode_path + (DecodePathDefBy(defined_by),), offset=offset, ) value.defined = (defined_by, defined_value) @@ -4397,7 +4453,7 @@ def generic_decoder(): # pragma: no cover __slots__ = () schema = choice - def pprint_any(obj, oids=None): + def pprint_any(obj, oids=None, with_colours=False): def _pprint_pps(pps): for pp in pps: if hasattr(pp, "_fields"): @@ -4411,6 +4467,7 @@ def generic_decoder(): # pragma: no cover oids=oids, with_offsets=True, with_blob=False, + with_colours=with_colours, ) for row in pp_console_blob(pp): yield row @@ -4442,6 +4499,11 @@ def main(): # pragma: no cover "--defines-by-path", help="Python path to decoder's defines_by_path", ) + parser.add_argument( + "--with-colours", + action='store_true', + help="Enable coloured output", + ) parser.add_argument( "DERFile", type=argparse.FileType("rb"), @@ -4465,7 +4527,11 @@ def main(): # pragma: no cover {"defines_by_path": obj_by_path(args.defines_by_path)} ), ) - print(pprinter(obj, oids=oids)) + print(pprinter( + obj, + oids=oids, + with_colours=True if args.with_colours else False, + )) if tail != b"": print("\nTrailing data: %s" % hexenc(tail)) diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 13322d7..18ce656 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -57,8 +57,8 @@ from pyderasn import BMPString from pyderasn import Boolean from pyderasn import BoundsError from pyderasn import Choice -from pyderasn import decode_path_defby from pyderasn import DecodeError +from pyderasn import DecodePathDefBy from pyderasn import Enumerated from pyderasn import GeneralizedTime from pyderasn import GeneralString @@ -5017,7 +5017,7 @@ class TestDefinesByPath(TestCase): self.assertIsNone(seq_inner["valueInner"].defined) defines_by_path.append(( - ("value", decode_path_defby(type_sequenced), "typeInner"), + ("value", DecodePathDefBy(type_sequenced), "typeInner"), ((("valueInner",), {type_innered: Pairs()}),), )) seq_sequenced, _ = Seq().decode( @@ -5036,9 +5036,9 @@ class TestDefinesByPath(TestCase): defines_by_path.append(( ( "value", - decode_path_defby(type_sequenced), + DecodePathDefBy(type_sequenced), "valueInner", - decode_path_defby(type_innered), + DecodePathDefBy(type_innered), any, "type", ),