Commits

Mikhail Korobov committed 7903dde

'psd-tools.py debug' command; optional namedtuple pretty-printing using IPython.lib.pretty

  • Participants
  • Parent commits 6dad115

Comments (0)

Files changed (6)

File src/psd_tools/cli.py

 from __future__ import absolute_import, unicode_literals, print_function
 import logging
 import docopt
-import pprint
 
 import psd_tools.reader
 import psd_tools.decoder
 from psd_tools import PSDImage
 from psd_tools.user_api.layers import group_layers
+from psd_tools.debug import pprint
 
 logger = logging.getLogger('psd_tools')
 logger.addHandler(logging.StreamHandler())
     psd-tools.py
 
     Usage:
-        psd-tools.py <filename> [--encoding <encoding>] [--verbose]
-        psd-tools.py convert <psd_filename> <out_filename> [--verbose]
-        psd-tools.py export_layer <psd_filename> <layer_index> <out_filename> [--verbose]
+        psd-tools.py convert <psd_filename> <out_filename> [options]
+        psd-tools.py export_layer <psd_filename> <layer_index> <out_filename> [options]
+        psd-tools.py debug <filename> [options]
         psd-tools.py -h | --help
         psd-tools.py --version
 
         logger.setLevel(logging.DEBUG)
     else:
         logger.setLevel(logging.INFO)
+    encoding = args['--encoding']
 
     if args['convert']:
-        psd = PSDImage.load(args['<psd_filename>'])
+        psd = PSDImage.load(args['<psd_filename>'], encoding=encoding)
         im = psd.as_PIL()
         im.save(args['<out_filename>'])
 
     elif args['export_layer']:
-        psd = PSDImage.load(args['<psd_filename>'])
+        psd = PSDImage.load(args['<psd_filename>'], encoding=encoding)
         index = int(args['<layer_index>'])
         im = psd.layers[index].as_PIL()
         im.save(args['<out_filename>'])
 
         psd.as_PIL()
 
-    else:
-        encoding = args['--encoding']
+    elif args['debug']:
         with open(args['<filename>'], "rb") as f:
             decoded = psd_tools.decoder.parse(
                 psd_tools.reader.parse(f, encoding)
             )
 
+        print("\nHeader\n------")
         print(decoded.header)
-        pprint.pprint(decoded.image_resource_blocks)
-        pprint.pprint(decoded.layer_and_mask_data)
-        pprint.pprint(decoded.image_data)
-        pprint.pprint(group_layers(decoded))
+        print("\nDecoded data\n-----------")
+        pprint(decoded)
+        print("\nLayers\n------")
+        pprint(group_layers(decoded))
 

File src/psd_tools/debug.py

+# -*- coding: utf-8 -*-
+"""
+Assorted debug utilities
+"""
+from __future__ import absolute_import
+import sys
+from collections import namedtuple
+try:
+    from IPython.lib.pretty import pprint
+    _PRETTY_ENABLED = True
+except ImportError:
+    from pprint import pprint
+    _PRETTY_ENABLED = False
+
+
+def debug_view(fp, txt="", max_back=20):
+    """
+    Print file contents around current position for file pointer ``fp``
+    """
+    max_back = min(max_back, fp.tell())
+    fp.seek(-max_back, 1)
+    pre = fp.read(max_back)
+    post = fp.read(100)
+    fp.seek(-100, 1)
+    print(txt, repr(pre), "--->.<---", repr(post))
+
+
+def pretty_namedtuple(typename, field_names, verbose=False):
+    """
+    Return a namedtuple class that knows how to pretty-print itself
+    using IPython.lib.pretty library; if IPython is not installed
+    then this function is the same as collections.namedtuple
+    (with one exception: 'rename' argument is unsupported).
+    """
+    cls = namedtuple(typename, field_names, verbose)
+    if _PRETTY_ENABLED:
+        PrettyMixin = _get_pretty_mixin(typename)
+        cls = type(str(typename), (PrettyMixin, cls), {})
+
+    # For pickling to work, the __module__ variable needs to be set to the frame
+    # where the named tuple is created.  Bypass this step in enviroments where
+    # sys._getframe is not defined (Jython for example) or sys._getframe is not
+    # defined for arguments greater than 0 (IronPython).
+    try:
+        cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
+    except (AttributeError, ValueError):
+        pass
+
+    return cls
+
+
+def _get_pretty_mixin(typename):
+    """
+    Return a mixin class for multiline pretty-printing
+    of namedtuple objects.
+    """
+    class _PrettyNamedtupleMixin(object):
+        def _repr_pretty_(self, p, cycle):
+            if cycle:
+                return "{typename}(...)".format(name=typename)
+
+            with p.group(1, '{name}('.format(name=typename), ')'):
+                p.breakable()
+                for idx, field in enumerate(self._fields):
+                    if idx:
+                        p.text(',')
+                        p.breakable()
+                    p.text('{field}='.format(field=field))
+                    p.pretty(getattr(self, field))
+
+    return _PrettyNamedtupleMixin
+
+

File src/psd_tools/decoder/tagged_blocks.py

 
 from psd_tools.constants import TaggedBlock, SectionDivider
 from psd_tools.decoder.actions import decode_descriptor, UnknownOSType
-from psd_tools.utils import read_fmt, read_unicode_string, unpack, debug_view
+from psd_tools.utils import read_fmt, read_unicode_string, unpack
 from psd_tools.decoder import decoders
 from psd_tools.reader.layers import Block
 

File src/psd_tools/reader/layers.py

 # -*- coding: utf-8 -*-
 from __future__ import absolute_import, unicode_literals, division, print_function
-import collections
 import logging
 import warnings
 import zlib
 
 from psd_tools.utils import (read_fmt, read_pascal_string,
-                             read_be_array, trimmed_repr, pad, synchronize,
-                             debug_view)
+                             read_be_array, trimmed_repr, pad, synchronize)
 from psd_tools.exceptions import Error
 from psd_tools.constants import (Compression, Clipping, BlendMode,
                                  ChannelID, TaggedBlock)
 from psd_tools import compression
+from psd_tools.debug import pretty_namedtuple
 
 logger = logging.getLogger(__name__)
 
-_LayerRecord = collections.namedtuple('LayerRecord', [
+_LayerRecord = pretty_namedtuple('LayerRecord', [
     'top', 'left', 'bottom', 'right',
     'num_channels', 'channels',
     'blend_mode', 'opacity', 'clipping', 'flags',
         return self.bottom - self.top
 
 
-Layers = collections.namedtuple('Layers', 'length, layer_count, layer_records, channel_image_data')
-LayerFlags = collections.namedtuple('LayerFlags', 'transparency_protected visible')
-LayerAndMaskData = collections.namedtuple('LayerAndMaskData', 'layers global_mask_info tagged_blocks')
-ChannelInfo = collections.namedtuple('ChannelInfo', 'id length')
-_MaskData = collections.namedtuple('MaskData', 'top left bottom right default_color flags real_flags real_background')
-LayerBlendingRanges = collections.namedtuple('LayerBlendingRanges', 'composite_ranges channel_ranges')
-_ChannelData = collections.namedtuple('ChannelData', 'compression data')
-_Block = collections.namedtuple('Block', 'key data')
-GlobalMaskInfo = collections.namedtuple('GlobalMaskInfo', 'overlay color_components opacity kind')
+Layers = pretty_namedtuple('Layers', 'length, layer_count, layer_records, channel_image_data')
+LayerFlags = pretty_namedtuple('LayerFlags', 'transparency_protected visible')
+LayerAndMaskData = pretty_namedtuple('LayerAndMaskData', 'layers global_mask_info tagged_blocks')
+ChannelInfo = pretty_namedtuple('ChannelInfo', 'id length')
+_MaskData = pretty_namedtuple('MaskData', 'top left bottom right default_color flags real_flags real_background')
+LayerBlendingRanges = pretty_namedtuple('LayerBlendingRanges', 'composite_ranges channel_ranges')
+_ChannelData = pretty_namedtuple('ChannelData', 'compression data')
+_Block = pretty_namedtuple('Block', 'key data')
+GlobalMaskInfo = pretty_namedtuple('GlobalMaskInfo', 'overlay color_components opacity kind')
 
 class MaskData(_MaskData):
 
             len(self.data) if self.data is not None else None
         )
 
+    def _repr_pretty_(self, p, cycle):
+        if cycle:
+            p.text('ChannelData(...)')
+        else:
+            p.text(repr(self))
+
+
 class Block(_Block):
     """
     Layer tagged block with extra info.
         return "Block(%s %s, %s)" % (self.key, TaggedBlock.name_of(self.key),
                                      trimmed_repr(self.data))
 
+    def _repr_pretty_(self, p, cycle):
+        if cycle:
+            p.text('Block(...)')
+        else:
+            p.text(repr(self))
+
 
 def read(fp, encoding, depth):
     """

File src/psd_tools/reader/reader.py

 # -*- coding: utf-8 -*-
 from __future__ import absolute_import, unicode_literals, division
 import logging
-import collections
 
 import psd_tools.reader.header
 import psd_tools.reader.color_mode_data
 import psd_tools.reader.image_resources
 import psd_tools.reader.layers
+from psd_tools.debug import pretty_namedtuple
 
 logger = logging.getLogger(__name__)
 
-ParseResult = collections.namedtuple(
+ParseResult = pretty_namedtuple(
     'ParseResult',
     'header, color_data, image_resource_blocks, layer_and_mask_data, image_data'
 )

File src/psd_tools/utils.py

     lo, hi = unpack("2H", data)
     # XXX: shouldn't denominator be 2**16 ?
     return lo + hi / (2**16 - 1)
-
-
-def debug_view(fp, txt="", max_back=20):
-    max_back = min(max_back, fp.tell())
-    fp.seek(-max_back, 1)
-    pre = fp.read(max_back)
-    post = fp.read(100)
-    fp.seek(-100, 1)
-    print(txt, repr(pre), "--->.<---", repr(post))