├── .gitignore ├── LICENSE.txt ├── MANIFEST ├── beeprint ├── __init__.py ├── config.py ├── constants.py ├── debug_kit.py ├── helper.py ├── helpers │ ├── __init__.py │ └── string.py ├── lib │ ├── __init__.py │ ├── position_dict.py │ ├── search_up_tree.py │ ├── test_main.py │ └── walker.py ├── models │ ├── __init__.py │ ├── block.py │ └── wrapper.py ├── printer.py ├── terminal_size.py └── utils.py ├── docs ├── .gitignore ├── Makefile ├── conf.py ├── diary.md ├── images │ └── a_real_world_example.png ├── index.rst └── make.bat ├── makefile ├── readme.rst ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── cmp_to_pprint.py ├── data ├── all_in_one.txt ├── auto_clip │ ├── by_lines.txt │ └── no_room.txt ├── class │ ├── all_repr_disable.txt │ ├── inst_repr_enable.txt │ └── last_el.txt ├── dict │ ├── comma.txt │ └── foo.txt ├── inline_repr │ └── out_of_range.txt ├── out_of_range_in_dict.txt ├── out_of_range_py2.txt ├── out_of_range_py3.txt ├── recursion.txt ├── string_break │ └── boundary_break.txt └── tuple │ ├── nested.txt │ └── simple.txt ├── definition.py ├── main.py ├── t.py ├── test_helper.py └── test_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.pyc 3 | __pycache__/ 4 | beeprint.egg-info/* 5 | build/* 6 | dist/* 7 | py310/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyanyany/beeprint/54c38876b3c8f2009bb74dec1dffbc5ee8086498/LICENSE.txt -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyanyany/beeprint/54c38876b3c8f2009bb74dec1dffbc5ee8086498/MANIFEST -------------------------------------------------------------------------------- /beeprint/__init__.py: -------------------------------------------------------------------------------- 1 | from .printer import pp 2 | from .utils import pyv 3 | from .config import Config 4 | -------------------------------------------------------------------------------- /beeprint/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | import sys 7 | import types 8 | 9 | from io import StringIO 10 | from . import constants as C 11 | from . import utils 12 | 13 | 14 | 15 | class Config(object): 16 | 17 | def clone(self): 18 | obj = Config() 19 | for k,v in self.__dict__.items(): 20 | setattr(obj, k, v) 21 | return obj 22 | 23 | # >> coding 24 | encoding = 'utf-8' 25 | 26 | # >> representation 27 | max_depth = 5 28 | indent_char = u' ' 29 | newline = False 30 | tuple_in_line = True 31 | list_in_line = True 32 | 33 | # >> stream 34 | stream = sys.stdout 35 | 36 | # >> class control 37 | 38 | # filter out attributes by prefix 39 | prop_leading_filters = ["__", "func_"] 40 | # filter out attributes by name or judgement function 41 | prop_filters = [utils.is_pan_function, 'im_func', 'im_self', 'im_class'] 42 | 43 | # call user-defined __repr__() on class 44 | # class_repr_enable = False 45 | 46 | # call user-defined __repr__() on class instance 47 | instance_repr_enable = True 48 | 49 | # >> 优先策略 50 | # to raise exception or not when errors happened 51 | # _PS_CONTENT_FIRST will keep content printing despite of any error 52 | priority_strategy = C._PS_CONTENT_FIRST 53 | 54 | # debug = False 55 | debug_level = 0 56 | debug_stream = StringIO() 57 | # print info after pp() 58 | debug_delay = True 59 | 60 | # >> string control 61 | # united_str_coding_representation 62 | # In spite of python version 63 | # unicode string will be displayed as u'' 64 | # non-unicode string will be displayed as b'' 65 | united_str_coding_representation = True 66 | str_display_lqm = '\'' 67 | str_display_rqm = '\'' 68 | str_display_not_prefix_u = True 69 | str_display_not_prefix_b = True 70 | str_display_escape_special_char = True 71 | 72 | element_display_last_with_comma = True 73 | 74 | # >> long string control 75 | string_break_enable = True 76 | string_break_method = C._STRING_BREAK_BY_TERMINAL 77 | string_break_width = 80 78 | 79 | # >> auto clip text 80 | text_autoclip_enable = True 81 | text_autoclip_method = C._TEXT_AUTOCLIP_BY_LINE 82 | # enabled if text_autoclip_method is _TEXT_AUTOCLIP_BY_LINE 83 | # and string_break_enable is True 84 | text_autoclip_maxline = 2 85 | # enabled if text_autoclip_method is _TEXT_AUTOCLIP_BY_LENGTH 86 | # text_autoclip_maxlength = 80 87 | 88 | # >> dict 89 | # print dict with ordered keys 90 | dict_ordered_key_enable = True 91 | 92 | # >> output control 93 | output_briefly_enable = True 94 | output_briefly_method = C._OB_BY_LEVEL 95 | -------------------------------------------------------------------------------- /beeprint/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | 7 | # >> 优先策略 8 | # 正确性优先,尽量正确显示出所有字段,有无法解析的字段立即报错并退出 9 | _PS_CORRECTNESS_FIRST = 1 10 | # 内容优先,尽量保证输出全文,对于无法解析的字段以预置内容代替 11 | # 比如: 12 | _PS_CONTENT_FIRST = 2 13 | 14 | # an element will occupy a single block 15 | # it has its own leading spaces 16 | _AS_ELEMENT_ = 1 17 | # compares to _AS_ELEMENT_, a value is a component of an element 18 | # it has not leading spaces except a span between a key 19 | # it belongs to a key 20 | _AS_VALUE_ = 1 << 1 21 | # when display, these elements need comma between each others 22 | # it must has a parent block 23 | # eg: [1, 2], {'key1': 'val1', 'key2': 'val2'}, (1, 2) 24 | _AS_LIST_ELEMENT_ = \ 25 | _AS_TUPLE_ELEMENT_ = 1 << 2 26 | 27 | _AS_DICT_ELEMENT_ = 1 << 3 28 | _AS_CLASS_ELEMENT_ = 1 << 4 29 | 30 | # debug level 31 | _DL_MODULE_ = 1 32 | _DL_FUNC_ = 2 33 | _DL_STATEMENT = 3 34 | 35 | # long string 36 | _STRING_BREAK_BY_NONE = 0 37 | _STRING_BREAK_BY_TERMINAL = 1 38 | # accompany with S.text_wrap_width argument 39 | _STRING_BREAK_BY_WIDTH = 2 40 | 41 | _TEXT_AUTOCLIP_BY_LINE = 1 42 | # _TEXT_AUTOCLIP_BY_LENGTH = 2 43 | 44 | # output briefly 45 | # print parallel levels that would not run out of view port 46 | # they're sub levels would be hidden 47 | _OB_BY_LEVEL = 1 48 | # print one level and its children levels until it reach the bottom of view port 49 | # just like echo $obj | less 50 | _OB_BY_HEIGHT = 2 51 | -------------------------------------------------------------------------------- /beeprint/debug_kit.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | 7 | import inspect 8 | import sys 9 | import re 10 | 11 | from . import constants as C 12 | from .utils import print_exc_plus, get_name, has_parent_class 13 | 14 | 15 | ### global variables 16 | G_leading_char = ' ' 17 | 18 | def add_leading(depth, text): 19 | text = G_leading_char*depth + text 20 | text = text.replace('\n', '\n' + G_leading_char*depth) 21 | return text 22 | 23 | def debug(config, level, depth, text): 24 | if config.debug_level >= level: 25 | frame_list = inspect.stack() 26 | frame_obj = frame_list[1][0] 27 | class_name = frame_obj.f_locals['self'].__class__.__name__ 28 | caller_name = frame_list[1][3] 29 | depth = len(frame_list) - 6 30 | if level == C._DL_FUNC_: 31 | depth -= 1 32 | text = class_name + '.' + caller_name + ': ' + text 33 | text = add_leading(depth, text) 34 | config.debug_stream.write(text + '\n') 35 | 36 | def print_obj_path(): 37 | tb = sys.exc_info()[2] 38 | print() 39 | # print_exc_plus() 40 | 41 | # >> get traceback frame list 42 | while True: 43 | if not tb.tb_next: 44 | break 45 | tb = tb.tb_next 46 | 47 | stack = [] 48 | f = tb.tb_frame 49 | while f: 50 | stack.append(f) 51 | f = f.f_back 52 | stack.reverse() 53 | 54 | # >> get caller frame info to find variable passed to pp() 55 | caller_frameinfo = None 56 | for frame in stack: 57 | fi = inspect.getframeinfo(frame) 58 | if fi.filename.endswith("printer.py"): 59 | break 60 | caller_frameinfo = fi 61 | groups = re.search(".*pp\((.*),.*\).*", caller_frameinfo.code_context[0]) 62 | passed_arg_name = groups.group(1) 63 | 64 | # >> get subclass of Block and ctx.obj 65 | trace = [] 66 | trace_cnt = 0 67 | repeat = {} 68 | for frame in stack: 69 | self = frame.f_locals.get('self') 70 | if (not self 71 | or id(self) in repeat 72 | or get_name(self) == 'Block' 73 | or not has_parent_class(self, 'Block') 74 | ): 75 | continue 76 | repeat[id(self)] = True 77 | obj = self.ctx.obj 78 | trace.append((self, obj)) 79 | 80 | if len(trace) == 0: 81 | return 82 | 83 | s_trace = passed_arg_name 84 | print(s_trace, end='') 85 | last = trace[0][1] 86 | trace = trace[1:-1] 87 | for info in trace: 88 | obj = info[1] 89 | pos = '' 90 | if last == obj: 91 | continue 92 | if isinstance(last, (tuple, list)): 93 | pos = "index:%s" % last.index(obj) 94 | last = obj 95 | elif isinstance(last, dict): 96 | pos = "key:%s" % repr(obj[0]) 97 | last = obj[1] 98 | else: 99 | # last object is a class or a object 100 | # according to beeprint block's process: 101 | # 102 | # object(A): 103 | # K1: V1 104 | # 105 | # when last = A, 106 | # first time obj = (K1, V1) 107 | # second time obj = V1 108 | pos = "attr:%s" % repr(obj[0]) 109 | last = obj[1] # next loop, it would be last == obj 110 | n = " -> " + pos 111 | print(n, end='') 112 | s_trace += n 113 | 114 | return s_trace 115 | 116 | 117 | def print_frame(f, limit=160): 118 | attrs = [attr for attr in dir(f) if not attr.startswith('__')] 119 | for attr in attrs: 120 | info = repr(getattr(f, attr)) 121 | print("%s: %s" % (attr, info[:limit])) 122 | print() 123 | 124 | 125 | -------------------------------------------------------------------------------- /beeprint/helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | import inspect 7 | import sys 8 | import types 9 | import urwid 10 | 11 | from . import constants as C 12 | from .utils import pyv, _unicode 13 | from .terminal_size import get_terminal_size 14 | from .lib import search_up_tree as sut 15 | from .config import Config 16 | # from kitchen.text import display 17 | 18 | if pyv == 2: 19 | range = xrange 20 | 21 | 22 | def ustr(s, config=Config()): 23 | '''convert string into unicode''' 24 | res = u'' 25 | 26 | try: 27 | if pyv == 2: 28 | if isinstance(s, _unicode): 29 | res = s 30 | else: # bytes 31 | res = s.decode(config.encoding) 32 | else: 33 | if isinstance(s, str): 34 | res = s 35 | else: # bytes 36 | res = s.decode(config.encoding) 37 | except: 38 | res = repr(s).strip("b'") 39 | return res 40 | 41 | def long_string_wrapper(ls, how): 42 | if how == C._LS_WRAP_BY_80_COLUMN: 43 | pass 44 | pass 45 | 46 | def tail_symbol(position): 47 | """calculate the tail of block 48 | newline character does not include here because 49 | when your calculate the length of whole line you only need to be 50 | care about printable characters 51 | """ 52 | if (position & C._AS_LIST_ELEMENT_ or 53 | position & C._AS_DICT_ELEMENT_ or 54 | position & C._AS_CLASS_ELEMENT_ or 55 | position & C._AS_TUPLE_ELEMENT_): 56 | tail = u',' 57 | else: 58 | tail = u'' 59 | return tail 60 | 61 | 62 | def is_extendable(obj): 63 | '判断obj是否可以展开' 64 | return isinstance(obj, dict) or hasattr(obj, '__dict__') or isinstance(obj, (tuple, list, types.FrameType)) 65 | 66 | 67 | def object_attr_default_filter(config, obj, name, val): 68 | '过滤不需要的对象属性' 69 | 70 | for propLeading in config.prop_leading_filters: 71 | if name.startswith(propLeading): 72 | return True 73 | 74 | for prop in config.prop_filters: 75 | # filter is a string 76 | if isinstance(prop, str) or isinstance(prop, _unicode): 77 | if name == prop: 78 | return True 79 | # filter is a type 80 | # if type(prop) == types.TypeType: 81 | if isinstance(prop, type): 82 | if type(val) == prop: 83 | return True 84 | # filter is callable 85 | elif hasattr(prop, '__call__'): 86 | if prop(name, val): 87 | return True 88 | 89 | return False 90 | 91 | def dict_key_filter(obj, name, val): 92 | return False 93 | 94 | def _b(config, s): 95 | if config and config.stream: 96 | config.stream.write(s) 97 | config.stream.flush() 98 | return s 99 | -------------------------------------------------------------------------------- /beeprint/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyanyany/beeprint/54c38876b3c8f2009bb74dec1dffbc5ee8086498/beeprint/helpers/__init__.py -------------------------------------------------------------------------------- /beeprint/helpers/string.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | 7 | import urwid 8 | import unicodedata 9 | 10 | from beeprint import constants as C 11 | from beeprint import helper 12 | from beeprint.utils import pyv, _unicode 13 | from beeprint.terminal_size import get_terminal_size 14 | 15 | 16 | def calc_width(s): 17 | if hasattr(urwid, 'util'): 18 | return urwid.util.calc_width(s, 0, len(s)) 19 | return urwid.str_util.calc_width(s, 0, len(s)) 20 | 21 | 22 | def cut_string(s, length): 23 | assert length > 0, "length must be positive" 24 | 25 | t = urwid.Text(s) 26 | seg_list = t.render((length,)).text 27 | a_str = seg_list[0].rstrip().decode('utf8') 28 | if pyv == 2: 29 | # to unicode 30 | a_str = a_str.decode('utf8') 31 | return a_str 32 | 33 | 34 | def too_long(indent_char, indent_cnt, position, inst_of_repr_block): 35 | indent_str = indent_cnt*indent_char 36 | 37 | choas = u'' 38 | # assumpts its the value of list 39 | if position & C._AS_VALUE_: 40 | choas += '[' 41 | choas += '\'' 42 | 43 | terminal_x = get_terminal_size()[0] 44 | body = str(inst_of_repr_block) 45 | whole_line_x = len(indent_str) + len(body) 46 | 47 | return terminal_x - whole_line_x <= 0 48 | 49 | 50 | def calc_left_margin(ctx, wrapper=None): 51 | left_filling = u''.join([ 52 | ctx.indent, 53 | ctx.key_expr, 54 | ctx.sep_expr, 55 | ]) 56 | left_margin = calc_width(left_filling) 57 | 58 | if wrapper is not None: 59 | left_margin += calc_width(wrapper.get_prefix()) 60 | 61 | return left_margin 62 | 63 | 64 | def calc_right_margin(ctx, wrapper=None): 65 | right_chars = '' 66 | if wrapper: 67 | right_chars += wrapper.get_suffix() 68 | 69 | right_chars += ctx.element_ending 70 | 71 | return calc_width(right_chars) 72 | 73 | 74 | def get_line_width(method, width=None): 75 | _width = 0 76 | if method == C._STRING_BREAK_BY_TERMINAL: 77 | _width = get_terminal_size()[0] 78 | elif method == C._STRING_BREAK_BY_WIDTH: 79 | _width = width 80 | 81 | return _width 82 | 83 | 84 | def shrink_string(s, width): 85 | """cut string, and show how many have been cut, 86 | the result length of string would not longer than width 87 | """ 88 | 89 | s_width = calc_width(s) 90 | if s_width > width: 91 | info = u'...(len=%d)' % (s_width) 92 | length = width - calc_width(info) 93 | if length > 0: 94 | s = cut_string(s, length) 95 | elif length == 0: 96 | s = '' 97 | else: 98 | return (s, -1*length) 99 | 100 | s += info 101 | 102 | return (s, 0) 103 | 104 | 105 | def shrink_inner_string(ctx, method, width=None, wrapper=None): 106 | s = ctx.obj 107 | left_margin = calc_left_margin(ctx, wrapper) 108 | right_margin = calc_right_margin(ctx, wrapper) 109 | 110 | # calculate availiable width for string 111 | a_width = get_line_width(method, width) - left_margin - right_margin 112 | 113 | if a_width <= 0: 114 | a_width = 1 # 1 just a random value to make it positive 115 | 116 | s, lack = shrink_string(s, a_width) 117 | if lack: 118 | s, lack = shrink_string(s, a_width + lack) 119 | 120 | ctx.obj = s 121 | return s 122 | 123 | 124 | def break_string(s, width): 125 | """break string into segments but would not break word 126 | works well with Chinese, English 127 | """ 128 | 129 | t = urwid.Text(s) 130 | seg_list = t.render((width,)).text 131 | seg_list = [seg.rstrip() for seg in seg_list] 132 | if pyv == 3: 133 | # seg is the type of in py3 134 | # seg is the type of in py2 135 | seg_list = [seg.decode('utf8') for seg in seg_list] 136 | return seg_list 137 | 138 | 139 | # copy from https://github.com/wolever/pprintpp 140 | # 141 | # pprintpp will make an attempt to print as many Unicode characters as is 142 | # safely possible. It will use the character category along with this table to 143 | # determine whether or not it is safe to print a character. In this context, 144 | # "safety" is defined as "the character will appear visually distinct" - 145 | # combining characters, spaces, and other things which could be visually 146 | # ambiguous are repr'd, others will be printed. I made this table mostly by 147 | # hand, mostly guessing, so please file bugs. 148 | # Source: http://www.unicode.org/reports/tr44/#GC_Values_Table 149 | unicode_printable_categories = { 150 | "Lu": 1, # Uppercase_Letter an uppercase letter 151 | "Ll": 1, # Lowercase_Letter a lowercase letter 152 | "Lt": 1, # Titlecase_Letter a digraphic character, with first part uppercase 153 | "LC": 1, # Cased_Letter Lu | Ll | Lt 154 | "Lm": 0, # Modifier_Letter a modifier letter 155 | "Lo": 1, # Other_Letter other letters, including syllables and ideographs 156 | "L": 1, # Letter Lu | Ll | Lt | Lm | Lo 157 | "Mn": 0, # Nonspacing_Mark a nonspacing combining mark (zero advance width) 158 | "Mc": 0, # Spacing_Mark a spacing combining mark (positive advance width) 159 | "Me": 0, # Enclosing_Mark an enclosing combining mark 160 | "M": 1, # Mark Mn | Mc | Me 161 | "Nd": 1, # Decimal_Number a decimal digit 162 | "Nl": 1, # Letter_Number a letterlike numeric character 163 | "No": 1, # Other_Number a numeric character of other type 164 | "N": 1, # Number Nd | Nl | No 165 | "Pc": 1, # Connector_Punctuation a connecting punctuation mark, like a tie 166 | "Pd": 1, # Dash_Punctuation a dash or hyphen punctuation mark 167 | "Ps": 1, # Open_Punctuation an opening punctuation mark (of a pair) 168 | "Pe": 1, # Close_Punctuation a closing punctuation mark (of a pair) 169 | "Pi": 1, # Initial_Punctuation an initial quotation mark 170 | "Pf": 1, # Final_Punctuation a final quotation mark 171 | "Po": 1, # Other_Punctuation a punctuation mark of other type 172 | "P": 1, # Punctuation Pc | Pd | Ps | Pe | Pi | Pf | Po 173 | "Sm": 1, # Math_Symbol a symbol of mathematical use 174 | "Sc": 1, # Currency_Symbol a currency sign 175 | "Sk": 1, # Modifier_Symbol a non-letterlike modifier symbol 176 | "So": 1, # Other_Symbol a symbol of other type 177 | "S": 1, # Symbol Sm | Sc | Sk | So 178 | "Zs": 0, # Space_Separator a space character (of various non-zero widths) 179 | "Zl": 0, # Line_Separator U+2028 LINE SEPARATOR only 180 | "Zp": 0, # Paragraph_Separator U+2029 PARAGRAPH SEPARATOR only 181 | "Z": 1, # Separator Zs | Zl | Zp 182 | "Cc": 0, # Control a C0 or C1 control code 183 | "Cf": 0, # Format a format control character 184 | "Cs": 0, # Surrogate a surrogate code point 185 | "Co": 0, # Private_Use a private-use character 186 | "Cn": 0, # Unassigned a reserved unassigned code point or a noncharacter 187 | "C": 0, # Other Cc | Cf | Cs | Co | Cn 188 | } 189 | 190 | def is_printable(char): 191 | return unicode_printable_categories.get(unicodedata.category(char)) 192 | -------------------------------------------------------------------------------- /beeprint/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyanyany/beeprint/54c38876b3c8f2009bb74dec1dffbc5ee8086498/beeprint/lib/__init__.py -------------------------------------------------------------------------------- /beeprint/lib/position_dict.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | 7 | from collections import OrderedDict 8 | 9 | 10 | class PositionDict(OrderedDict): 11 | 12 | def __init__(self, tlist): 13 | idx_by_key = {} 14 | key_by_idx = {} 15 | for idx, tup in enumerate(tlist): 16 | key = tup[0] 17 | idx_by_key[key] = idx 18 | key_by_idx[idx] = key 19 | 20 | self.idx_by_key = idx_by_key 21 | self.key_by_idx = key_by_idx 22 | 23 | super(PositionDict, self).__init__(tlist) 24 | 25 | def idx(self, key): 26 | return self.idx_by_key[key] 27 | 28 | def indexed_items(self): 29 | for key, val in super(PositionDict, self).items(): 30 | idx = self.idx(key) 31 | yield idx, key, val 32 | 33 | def index(self, pair): 34 | return self.idx(pair[0]) 35 | 36 | def __iter__(self): 37 | for key in super(PositionDict, self).__iter__(): 38 | val = self[key] 39 | idx = self.idx(key) 40 | yield idx, key, val 41 | -------------------------------------------------------------------------------- /beeprint/lib/search_up_tree.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | 7 | 8 | class Tree(object): 9 | 10 | def __init__(self, adict, values): 11 | self.d_node_by_name = {} 12 | self.build_tree(adict, -99999, self.d_node_by_name) 13 | self.values = values 14 | 15 | def build_tree(self, adict, root_name, d_node_by_name): 16 | if root_name in adict: 17 | raise Exception("root name conflicts with keys") 18 | 19 | for k, v in adict.items(): 20 | if k in d_node_by_name: 21 | raise Exception("invalid dict -- all keys must be unique") 22 | 23 | d_node_by_name[k] = root_name 24 | self.build_tree(v, k, d_node_by_name) 25 | 26 | def fetch_up(self, name): 27 | if name in self.values: 28 | return self.values[name] 29 | if name not in self.d_node_by_name: 30 | return None 31 | parent_name = self.d_node_by_name[name] 32 | return self.fetch_up(parent_name) 33 | -------------------------------------------------------------------------------- /beeprint/lib/test_main.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import unittest 4 | 5 | import search_up_tree as sut 6 | 7 | 8 | class TestUtils(unittest.TestCase): 9 | 10 | def setUp(self): 11 | pass 12 | 13 | def test_sut(self): 14 | 15 | h = { 16 | 'all': { 17 | 'string': { 18 | 'bytes': {}, 19 | 'unicode': {}, 20 | 'literal': {}, 21 | }, 22 | 'undefined': {}, 23 | }, 24 | } 25 | sut_tree = sut.Tree(h, {'string': 'hit_string'}) 26 | self.assertEqual(sut_tree.fetch_up('string'), 'hit_string') 27 | self.assertEqual(sut_tree.fetch_up('literal'), 'hit_string') 28 | self.assertEqual(sut_tree.fetch_up('undefined'), None) 29 | self.assertEqual(sut_tree.fetch_up('all'), None) 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /beeprint/lib/walker.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | 7 | 8 | class Walker(object): 9 | 10 | class Exit(Exception): pass 11 | class PrintNExit(Exception): pass 12 | -------------------------------------------------------------------------------- /beeprint/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyanyany/beeprint/54c38876b3c8f2009bb74dec1dffbc5ee8086498/beeprint/models/__init__.py -------------------------------------------------------------------------------- /beeprint/models/block.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | 7 | from io import StringIO 8 | 9 | import urwid 10 | import inspect 11 | 12 | from beeprint import utils 13 | from beeprint import helper 14 | from beeprint import constants as C 15 | from beeprint.config import Config 16 | from beeprint.debug_kit import debug 17 | from beeprint.utils import (is_newline_obj, is_class_instance, pyv, 18 | _unicode, get_name, get_type, has_custom_repr, 19 | is_base_type) 20 | from beeprint.helper import (object_attr_default_filter, dict_key_filter, 21 | ustr, is_extendable) 22 | from beeprint.lib import search_up_tree as sut 23 | from beeprint.lib import position_dict 24 | from beeprint.helpers.string import (break_string, 25 | calc_width, calc_left_margin, 26 | too_long, shrink_inner_string, 27 | get_line_width, is_printable) 28 | from beeprint.models.wrapper import StringWrapper 29 | 30 | 31 | class Block(object): 32 | ctx = None 33 | ctn = None 34 | 35 | def __init__(self, config, ctx): 36 | self.config = config 37 | self.ctx = ctx 38 | self.ctx.element_ending = self.get_element_ending() 39 | self.ctn = BlockContent(self) 40 | 41 | def __str__(self): 42 | return self.build_block() 43 | 44 | def get_block_ending(self, value=None): 45 | ending = u'\n' 46 | if self.ctx.position & C._AS_VALUE_: 47 | ending = u'' 48 | if self.ctx.position & C._AS_CLASS_ELEMENT_: 49 | 'last element of class has no ending' 50 | elements = self.ctx.parent.get_elements() 51 | if elements.index(self.ctx.obj) == len(elements)-1: 52 | ending = u'' 53 | return ending 54 | 55 | def get_element_ending(self, value=None): 56 | position = self.ctx.position 57 | tail = u'' 58 | if position & C._AS_VALUE_: 59 | tail = u'' 60 | elif (position & C._AS_LIST_ELEMENT_ or 61 | position & C._AS_DICT_ELEMENT_ or 62 | position & C._AS_CLASS_ELEMENT_ or 63 | position & C._AS_TUPLE_ELEMENT_): 64 | tail = u',' 65 | 66 | if False: 67 | pass 68 | elif position & C._AS_CLASS_ELEMENT_: 69 | 'last element of class has no ending' 70 | elements = self.ctx.parent.get_elements() 71 | if elements.index(self.ctx.obj) == len(elements)-1: 72 | tail = u'' 73 | elif is_class_instance(value) or utils.is_class(value): 74 | if self.ctx.indent_cnt >= self.config.max_depth: 75 | # if reach max depth, the value would be repr as one-line string 76 | # so comma is need 77 | pass 78 | else: 79 | tail = u'' 80 | # print('%s or %s: %s' % (is_class_instance(value), utils.is_class(value), value)) 81 | 82 | return tail 83 | 84 | def get_elements(self): 85 | raise Exception("%s does not implement this method" % 86 | self.__class__) 87 | 88 | def build_block(self): 89 | """遍历对象,判断对象内成员的类型,然后构造对应的 *Block""" 90 | 91 | indent_cnt = self.ctx.indent_cnt 92 | obj = self.ctx.obj 93 | position = self.ctx.position 94 | 95 | debug(self.config, C._DL_FUNC_, 96 | indent_cnt, 97 | ('obj:{} indent_cnt:{} position:{:b}'.format( 98 | obj, indent_cnt, position))) 99 | 100 | ret = ustr('') 101 | 102 | tail = self.ctx.element_ending 103 | block_ending = self.get_block_ending() 104 | debug(self.config, C._DL_STATEMENT, 105 | indent_cnt, 'tail, block_ending: ' + str([tail, block_ending])) 106 | 107 | # recursive check 108 | oid = id(obj) 109 | if oid in self.ctx.obj_ids: 110 | if self.config.newline or position & C._AS_ELEMENT_: 111 | ret = ustr(indent_cnt * self.config.indent_char) 112 | else: 113 | ret = ustr("") 114 | 115 | if is_base_type(obj): 116 | typename = '%s' % get_name(obj) 117 | else: 118 | typename = '%s(%s)' % (get_type(obj), get_name(obj)) 119 | ret += ustr("" % (typename, oid)) 120 | ret += tail + block_ending 121 | return self.ctn.write(ret) 122 | self.ctx.obj_ids = self.ctx.obj_ids.copy() 123 | self.ctx.obj_ids.add(oid) 124 | 125 | if self.config.max_depth <= indent_cnt: 126 | if self.config.newline or position & C._AS_ELEMENT_: 127 | ret = ustr(indent_cnt * self.config.indent_char) 128 | else: 129 | ret = ustr(" ") 130 | 131 | rb = ReprBlock(self.config, self.ctx, handlers=[ 132 | ReprStringHandlerInlineRepr(self.config), 133 | ReprOthersHandlerInlineRepr(self.config)]) 134 | ret += str(rb) + tail + '\n' 135 | return self.ctn.write(ret) 136 | 137 | if isinstance(obj, dict): 138 | debug(self.config, C._DL_STATEMENT, indent_cnt, 'is dict') 139 | ret += str(DictBlock(self.config, self.ctx)) 140 | elif isinstance(obj, list): 141 | debug(self.config, C._DL_STATEMENT, indent_cnt, 'is list') 142 | ret += str(ListBlock(self.config, self.ctx)) 143 | elif isinstance(obj, tuple): 144 | debug(self.config, C._DL_STATEMENT, indent_cnt, 'is tuple') 145 | ret += str(TupleBlock(self.config, self.ctx)) 146 | elif is_extendable(obj): 147 | debug(self.config, C._DL_STATEMENT, indent_cnt, 'is extendable') 148 | ret += str(ClassBlock(self.config, self.ctx)) 149 | else: 150 | debug(self.config, C._DL_STATEMENT, indent_cnt, 'is simple type') 151 | rb = ReprBlock(self.config, self.ctx, handlers=[ 152 | ReprStringHandlerMultiLiner(self.config), 153 | ReprOthersHandler(self.config)]) 154 | ret += self.ctn.write(indent_cnt * self.config.indent_char + str(rb) + ustr(tail + '\n')) 155 | 156 | return ret 157 | 158 | class ClassBlock(Block): 159 | 160 | def get_elements(self): 161 | o = self.ctx.obj 162 | props = [] 163 | for attr_name in dir(o): 164 | if attr_name == '__abstractmethods__': 165 | continue 166 | 167 | try: 168 | attr = getattr(o, attr_name) 169 | except Exception as e: 170 | continue 171 | 172 | if object_attr_default_filter(self.config, o, attr_name, attr): 173 | continue 174 | 175 | props.append((attr_name, attr)) 176 | 177 | return position_dict.PositionDict(props) 178 | 179 | def get_element_ending(self): 180 | """A class block would have many nested elements, 181 | it would not know how its children respresent. 182 | for example: 183 | 184 | Example One: 185 | >>> class(A): 186 | ... class(B), 187 | 188 | Example Two: 189 | >>> class(A): 190 | ... class(B): 191 | ... prop: val 192 | 193 | As above, class(A) does not know and **should not know** whether class(B) has children 194 | 195 | So, the block of class(A) only need to do is just to add a newline to finish itself. 196 | """ 197 | if len(self.get_elements()) > 0: 198 | return u'' 199 | else: 200 | return super(self.__class__, self).get_element_ending() 201 | 202 | ''' 203 | def get_block_ending(self): 204 | return u'' 205 | ''' 206 | 207 | def build_block(self): 208 | indent_cnt = self.ctx.indent_cnt 209 | o = self.ctx.obj 210 | position = self.ctx.position 211 | 212 | debug(self.config, C._DL_FUNC_, indent_cnt, 213 | ('obj:{} indent_cnt:{} position:{:b}'.format( 214 | o, indent_cnt, position))) 215 | 216 | tail = self.ctx.element_ending 217 | block_ending = self.get_block_ending() 218 | debug(self.config, C._DL_STATEMENT, 219 | indent_cnt, 'tail, block_ending: ' + str([tail, block_ending])) 220 | 221 | # { 222 | _leading = ustr('') 223 | if position & C._AS_ELEMENT_: 224 | _leading += self.config.indent_char * indent_cnt 225 | elif position & C._AS_VALUE_: 226 | _leading += ustr('') 227 | 228 | name = get_name(o) 229 | label = get_type(o) 230 | 231 | ret = _leading 232 | if (self.config.instance_repr_enable and 233 | is_class_instance(self.ctx.obj) and 234 | has_custom_repr(self.ctx.obj)): 235 | ret = self.ctn.write(ret + ustr(repr(self.ctx.obj))) 236 | else: 237 | ret += '%s(%s):' % (label, name) + '\n' 238 | 239 | # body 240 | ele_ctnr = self.get_elements() 241 | props_cnt = len(ele_ctnr) 242 | 243 | if props_cnt == 0: 244 | # right strip ':\n' 245 | ret = ret[:-2] 246 | 247 | ret = self.ctn.write(ret) 248 | 249 | for idx, key, val in ele_ctnr: 250 | # '忽略掉 以__开头的成员、自引用成员、函数成员' 251 | ctx = self.ctx.clone() 252 | ctx.obj = (key, val) 253 | ctx.parent = self 254 | ctx.position = C._AS_CLASS_ELEMENT_ 255 | ctx.indent_cnt = self.ctx.indent_cnt + 1 256 | ret += str(PairBlock(self.config, ctx)) 257 | 258 | # } 259 | ret += self.ctn.write(tail + block_ending) 260 | return ret 261 | 262 | class DictBlock(Block): 263 | 264 | def __init__(self, config, ctx): 265 | ctx.__dict__.setdefault('position', C._AS_VALUE_) 266 | super(self.__class__, self).__init__(config, ctx) 267 | 268 | def get_elements(self): 269 | if not self.config.dict_ordered_key_enable: 270 | return self.ctx.obj.items() 271 | 272 | def items(self): 273 | keys = list(self.ctx.obj.keys()) 274 | try: 275 | keys.sort() 276 | except: 277 | # if keys elements are type() objects, it can not be sort 278 | keys.sort(key=lambda e: repr(e)) 279 | for k in keys: 280 | yield k, self.ctx.obj[k] 281 | return items(self) 282 | 283 | def build_block(self): 284 | 285 | indent_cnt = self.ctx.indent_cnt 286 | o = self.ctx.obj 287 | position = self.ctx.position 288 | 289 | debug(self.config, C._DL_FUNC_, indent_cnt, 290 | ('obj:{} indent_cnt:{} position:{:b}'.format( 291 | o, indent_cnt, position))) 292 | 293 | ret = ustr('') 294 | tail = self.ctx.element_ending 295 | block_ending = self.get_block_ending() 296 | debug(self.config, C._DL_STATEMENT, indent_cnt, 'tail, block_ending: ' + str([tail, block_ending])) 297 | # { 298 | if self.config.newline or position & C._AS_ELEMENT_: 299 | ret += self.ctn.write(self.config.indent_char * indent_cnt + ustr('{') + ustr('\n')) 300 | else: 301 | ret += self.ctn.write(ustr('{') + ustr('\n')) 302 | 303 | # body 304 | for k, v in self.get_elements(): 305 | if dict_key_filter(o, k, v): 306 | continue 307 | ctx = self.ctx.clone() 308 | ctx.obj = (k, v) 309 | ctx.parent = self 310 | ctx.position = C._AS_DICT_ELEMENT_ 311 | ctx.indent_cnt = self.ctx.indent_cnt + 1 312 | ret += str(PairBlock(self.config, ctx)) 313 | 314 | # } 315 | ret += self.ctn.write(self.config.indent_char * indent_cnt + '}' + ustr(tail + block_ending)) 316 | 317 | return ret 318 | 319 | class ListBlock(Block): 320 | 321 | def __init__(self, config, ctx): 322 | ctx.__dict__.setdefault('position', C._AS_VALUE_) 323 | super(self.__class__, self).__init__(config, ctx) 324 | 325 | def get_elements(self): 326 | return self.ctx.obj 327 | 328 | def build_block(self): 329 | indent_cnt = self.ctx.indent_cnt 330 | o = self.ctx.obj 331 | position = self.ctx.position 332 | ret = ustr('') 333 | 334 | tail = self.ctx.element_ending 335 | block_ending = self.get_block_ending() 336 | debug(self.config, 337 | C._DL_FUNC_, 338 | indent_cnt, 'tail, block_ending: ' + str([tail, block_ending])) 339 | 340 | '所有元素显示在同一行' 341 | if self.config.list_in_line: 342 | _f = map( 343 | lambda e: (not (is_extendable(e) or 344 | too_long(self.config.indent_char, 345 | indent_cnt, position, repr_block(e, self.config)))), 346 | o) 347 | if all(_f): 348 | _o = map(lambda e: repr_block(e, self.config), o) 349 | if self.config.newline or position & C._AS_ELEMENT_: 350 | ret += self.ctn.write(self.config.indent_char * indent_cnt) 351 | ret += self.ctn.write("[") 352 | for (i, e) in enumerate(_o): 353 | if i: 354 | ret += self.ctn.write(', ') 355 | ret += self.ctn.write(e) 356 | ret += self.ctn.write("]" + tail + block_ending) 357 | return ret 358 | 359 | # [ 360 | if self.config.newline or position & C._AS_ELEMENT_: 361 | ret += self.ctn.write(self.config.indent_char * indent_cnt + ustr('[') + ustr('\n')) 362 | else: 363 | ret += self.ctn.write(ustr('[') + ustr('\n')) 364 | 365 | # body 366 | for e in o: 367 | ctx = self.ctx.clone() 368 | ctx.obj = e 369 | ctx.parent = self 370 | ctx.indent_cnt = self.ctx.indent_cnt + 1 371 | ctx.position = C._AS_ELEMENT_ | C._AS_LIST_ELEMENT_ 372 | ret += str(Block(self.config, ctx)) 373 | 374 | # ] 375 | ret += self.ctn.write(self.config.indent_char * indent_cnt + ustr(']' + tail + block_ending)) 376 | 377 | return ret 378 | 379 | 380 | 381 | class TupleBlock(Block): 382 | 383 | def __init__(self, config, ctx): 384 | ctx.__dict__.setdefault('position', C._AS_VALUE_) 385 | super(self.__class__, self).__init__(config, ctx) 386 | 387 | def get_elements(self): 388 | return self.ctx.obj 389 | 390 | def build_block(self): 391 | indent_cnt = self.ctx.indent_cnt 392 | o = self.ctx.obj 393 | position = self.ctx.position 394 | 395 | debug(self.config, C._DL_FUNC_, 396 | indent_cnt, 397 | ('obj:{} indent_cnt:{} position:{:b}'.format( 398 | o, indent_cnt, position))) 399 | ret = ustr('') 400 | 401 | tail = self.ctx.element_ending 402 | block_ending = self.get_block_ending() 403 | debug(self.config, C._DL_STATEMENT, indent_cnt, 'tail, block_ending: ' + str([tail, block_ending])) 404 | 405 | if self.config.tuple_in_line: 406 | _f = map(lambda e: not is_extendable(e), o) 407 | if all(_f): 408 | _o = map(lambda e: repr_block(e, self.config), o) 409 | if self.config.newline or position & C._AS_ELEMENT_: 410 | ret += self.ctn.write(self.config.indent_char * indent_cnt) 411 | ret += self.ctn.write(ustr("(")) 412 | for (i, e) in enumerate(_o): 413 | if i: 414 | ret += self.ctn.write(', ') 415 | ret += self.ctn.write(e) 416 | ret += self.ctn.write(')' + tail + block_ending) 417 | return ret 418 | 419 | # ( 420 | if self.config.newline or position & C._AS_ELEMENT_: 421 | ret += self.ctn.write(self.config.indent_char * indent_cnt + ustr('(\n')) 422 | else: 423 | ret += self.ctn.write(ustr('(\n')) 424 | 425 | # body 426 | for e in self.get_elements(): 427 | ctx = self.ctx.clone() 428 | ctx.obj = e 429 | ctx.parent = self 430 | ctx.position = C._AS_ELEMENT_ | C._AS_TUPLE_ELEMENT_ 431 | ctx.indent_cnt += 1 432 | ret += str(Block(self.config, ctx)) 433 | 434 | # ) 435 | ret += self.ctn.write(self.config.indent_char * indent_cnt + ustr(')' + tail + block_ending)) 436 | 437 | return ret 438 | 439 | class PairBlock(Block): 440 | 441 | def __init__(self, *args, **kwargs): 442 | super(self.__class__, self).__init__(*args, **kwargs) 443 | self.ctx.key = self.ctx.obj[0] 444 | self.ctx.key_expr = self.get_key(self.ctx.position, self.ctx.key) 445 | 446 | def get_key(self, position, name): 447 | if position & C._AS_CLASS_ELEMENT_: 448 | # class method name or attribute name no need to add u or b prefix 449 | key = ustr(name) 450 | else: 451 | key = repr_block(name, self.config) 452 | 453 | return key 454 | 455 | def get_element_ending(self, value=None): 456 | value = value or self.ctx.obj[1] 457 | return super(self.__class__, self).get_element_ending(value) 458 | 459 | 460 | def build_block(self): 461 | indent_cnt = self.ctx.indent_cnt 462 | name, val = self.ctx.obj 463 | position = self.ctx.position 464 | 465 | debug(self.config, C._DL_FUNC_, indent_cnt, 466 | ('key:{}, indent_cnt:{}, position:{:b}'.format( 467 | name, indent_cnt, position))) 468 | ret = ustr('') 469 | 470 | tail = self.ctx.element_ending 471 | block_ending = self.get_block_ending(val) 472 | debug(self.config, C._DL_STATEMENT, 473 | indent_cnt, 'tail, block_ending: ' + str([tail, block_ending])) 474 | 475 | key = self.ctx.key_expr 476 | 477 | ret += self.ctn.write(self.config.indent_char * indent_cnt + key + ':') 478 | if is_extendable(val) and self.config.max_depth > indent_cnt: 479 | # still in of range, and can be expanded 480 | 481 | # value need to be dispalyed on new line 482 | # including: 483 | # class type & class instance 484 | # function type 485 | if self.config.newline or (is_newline_obj(val) and 486 | position & C._AS_ELEMENT_): 487 | ret += self.ctn.write(ustr('\n')) 488 | indent_cnt = indent_cnt + 1 489 | position = C._AS_ELEMENT_ 490 | debug(self.config, C._DL_STATEMENT, indent_cnt, 'make newline') 491 | # value will be dispalyed immediately after one space 492 | else: 493 | ret += self.ctn.write(ustr(" ")) 494 | position = C._AS_VALUE_ 495 | 496 | ctx = self.ctx.clone() 497 | ctx.obj = val 498 | ctx.parent = self 499 | ctx.position = position 500 | ctx.indent_cnt = indent_cnt 501 | ret += str(Block(self.config, ctx)) 502 | ret += self.ctn.write(tail + block_ending) 503 | else: 504 | ctx = self.ctx.clone() 505 | ctx.obj = val 506 | if self.config.max_depth <= indent_cnt: 507 | # reach max_depth, just show brief message 508 | rb = ReprBlock(self.config, ctx, 509 | handlers=[ReprStringHandlerInlineRepr(self.config), 510 | ReprOthersHandlerInlineRepr(self.config)]) 511 | ret += self.ctn.write(ustr(" " + str(rb) + tail + block_ending)) 512 | else: 513 | rb = ReprBlock(self.config, ctx, 514 | handlers=[ReprStringHandlerMultiLiner(self.config), 515 | ReprOthersHandler(self.config)]) 516 | ret += self.ctn.write(ustr(" ") + str(rb) + ustr(tail + block_ending)) 517 | 518 | return ret 519 | 520 | 521 | class Context(object): 522 | """Context contains info of current line displaying, 523 | it tells you where you are in the whole block 524 | it tells you what happened before on the same line 525 | it tells you what **will** happened after you on the same line 526 | """ 527 | 528 | def __init__(self, **attrs): 529 | # must explicitly set attributes to instance 530 | # or below attribute will always be set as C._AS_VALUE_: 531 | # 532 | # ctx.__dict__.setdefault('position', C._AS_VALUE_) 533 | # 534 | # which will always override the default value C._AS_ELEMENT_ 535 | 536 | self.obj = None 537 | self.parent = None 538 | 539 | self.indent_char = Config.indent_char 540 | self.position = C._AS_ELEMENT_ 541 | self.indent_cnt = 0 542 | 543 | self.seperator = ":" 544 | self.after_seperator = " " 545 | 546 | self.element_ending = None 547 | self.key = None 548 | self.key_expr = '' 549 | 550 | self.obj_ids = set() 551 | 552 | self.__dict__.update(**attrs) 553 | 554 | def clone(self): 555 | ctx = self.__class__(**self.__dict__) 556 | return ctx 557 | 558 | @property 559 | def indent(self): 560 | return self.indent_char*self.indent_cnt 561 | 562 | @property 563 | def sep_expr(self): 564 | if self.key is None: 565 | return '' 566 | return self.seperator + u' ' 567 | 568 | @property 569 | def val_expr(self): 570 | raise Exception("not yet implement") 571 | 572 | 573 | def repr_block(obj, config=Config()): 574 | return str(ReprBlock(config, Context(obj=obj))) 575 | 576 | 577 | class ReprBlock(Block): 578 | """like repr(), but provides more functions""" 579 | 580 | def __init__(self, config, ctx, handlers=None): 581 | self.config = config 582 | self.ctx = ctx 583 | self.handlers = handlers or [ 584 | ReprStringHandler(self.config), 585 | ReprOthersHandler(self.config)] 586 | self.typ = ReprBlock.ReprType(ctx.obj) 587 | 588 | def build_block(self): 589 | for handler in self.handlers: 590 | if handler.accept(self.typ): 591 | return handler(self.ctx, self.typ) 592 | 593 | raise Exception("no handlers") 594 | 595 | class Handler(object): 596 | 597 | def __init__(self, config): 598 | self.config = config 599 | 600 | def accept(self, typ): 601 | raise Exception("Unimplement method") 602 | 603 | def __call__(self, ctx, typ): 604 | raise Exception("Unimplement method") 605 | 606 | class ReprType(object): 607 | 608 | _LITERAL_ = 1 # string literal depends on script's coding 609 | _UNICODE_ = 2 610 | _BYTES_ = 4 611 | _OTHERS_ = 8 612 | 613 | typ = 0 614 | 615 | def __init__(self, obj): 616 | self.typ = self.judge(obj) 617 | 618 | def is_all(self, *args): 619 | bi_ands = map(lambda b: b & self.typ, args) 620 | return all(bi_ands) 621 | 622 | def is_any(self, *args): 623 | bi_ands = map(lambda b: b & self.typ, args) 624 | return any(bi_ands) 625 | 626 | def is_string(self): 627 | return self.is_any( 628 | self.__class__._LITERAL_, 629 | self.__class__._UNICODE_, 630 | self.__class__._BYTES_, 631 | ) 632 | 633 | @classmethod 634 | def judge(cls, obj): 635 | if pyv == 2: 636 | # in py2, string literal is both instance of str and bytes 637 | # a literal string is str (i.e: coding encoded, eg: utf8) 638 | # a u-prefixed string is unicode 639 | if isinstance(obj, _unicode): 640 | return cls._UNICODE_ 641 | elif isinstance(obj, str): # same as isinstance(v, bytes) 642 | return cls._LITERAL_ | cls._BYTES_ 643 | else: 644 | # in py3, 645 | # a literal string is str (i.e: unicode encoded) 646 | # a u-prefixed string is str 647 | # a utf8 string is bytes 648 | if isinstance(obj, bytes): 649 | return cls._BYTES_ 650 | elif isinstance(obj, str): 651 | return cls._LITERAL_ | cls._UNICODE_ 652 | 653 | return cls._OTHERS_ 654 | 655 | 656 | class ReprStringHandler(ReprBlock.Handler): 657 | """handle repr while processing string object like unicode, bytes, str""" 658 | 659 | def __init__(self, *args, **kwargs): 660 | # whether to quote the string 661 | self.do_quote = kwargs.pop('do_quote', None) or True 662 | super(ReprStringHandler, self).__init__(*args, **kwargs) 663 | 664 | def accept(self, typ): 665 | return typ.is_string() 666 | 667 | def escape(self, obj, typ): 668 | obj = obj.replace(u'\\', u'\\\\') 669 | obj = obj.replace(u'\n', u'\\n') 670 | obj = obj.replace(u'\r', u'\\r') 671 | obj = obj.replace(u'\t', u'\\t') 672 | 673 | return obj 674 | 675 | def __call__(self, ctx, typ): 676 | if typ.is_all(typ._BYTES_): 677 | if pyv == 2: 678 | # convert to unicode 679 | ctx.obj = ctx.obj.decode(self.config.encoding, 'replace') 680 | ctx.obj = self.escape(ctx.obj, typ) 681 | else: 682 | ctx.obj = repr(ctx.obj)[2:-1] 683 | else: 684 | sio = StringIO() 685 | for char in ctx.obj: 686 | if is_printable(char): 687 | char = self.escape(char, typ) 688 | sio.write(char) 689 | else: 690 | char = repr(char)[1:-1] 691 | if pyv == 2: 692 | char = char[1:] 693 | char = char.decode(self.config.encoding) 694 | sio.write(char) 695 | ctx.obj = sio.getvalue() 696 | sio.close() 697 | """ 698 | try: 699 | ctx.obj.encode('utf8') 700 | ctx.obj = self.escape(ctx.obj, typ) 701 | except: 702 | ctx.obj = repr(ctx.obj)[1:-1] 703 | """ 704 | 705 | if self.do_quote: 706 | wrapper = StringWrapper(self.config, typ) 707 | else: 708 | wrapper = StringWrapper(self.config, typ, lqm='', rqm='') 709 | 710 | return self.rearrenge(ctx, typ, wrapper) 711 | 712 | def rearrenge(self, ctx, typ, wrapper): 713 | return wrapper.wrap(ctx.obj) 714 | 715 | class ReprStringHandlerMultiLiner(ReprStringHandler): 716 | 717 | def rearrenge(self, ctx, typ, wrapper): 718 | assert ctx.element_ending is not None, "element_ending must be set" 719 | 720 | left_margin = calc_left_margin(ctx, wrapper) 721 | # calculate availiable width for string 722 | a_width = get_line_width(self.config.string_break_method, 723 | self.config.string_break_width) - left_margin 724 | 725 | if a_width > 0: 726 | seg_list = break_string( 727 | ctx.obj + 728 | wrapper.rqm + 729 | ctx.element_ending, 730 | a_width) 731 | 732 | # seg_list[-1] = seg_list[-1].rstrip( 733 | # wrapper.rqm + 734 | # ctx.element_ending, 735 | # ) 736 | # remove rqm + element_ending added by last block 737 | rqm_ending_len = len(wrapper.rqm)+len(ctx.element_ending) 738 | if seg_list[-1][-1*rqm_ending_len:] == wrapper.rqm + ctx.element_ending: 739 | seg_list[-1] = seg_list[-1][:-1*rqm_ending_len] 740 | indent_char_width = calc_width(self.config.indent_char) 741 | left_margin_chars = (left_margin // indent_char_width * self.config.indent_char + 742 | left_margin % indent_char_width * ' ') 743 | for i in range(1, len(seg_list)): 744 | seg_list[i] = ''.join([ 745 | left_margin_chars, 746 | seg_list[i], 747 | ]) 748 | 749 | if (self.config.text_autoclip_enable and 750 | self.config.text_autoclip_method == C._TEXT_AUTOCLIP_BY_LINE): 751 | lines_cnt = len(seg_list) 752 | if lines_cnt > self.config.text_autoclip_maxline: 753 | seg_list = seg_list[:self.config.text_autoclip_maxline] 754 | hidden_lines = lines_cnt - self.config.text_autoclip_maxline 755 | plural_sign = '' if hidden_lines == 1 else 's' 756 | seg_list.append("%s...(%d hidden line%s)" % (left_margin_chars, hidden_lines, plural_sign)) 757 | ctx.obj = "\n".join(seg_list) 758 | 759 | return wrapper.wrap(ctx.obj) 760 | 761 | 762 | class ReprOthersHandler(ReprBlock.Handler): 763 | 764 | def accept(self, typ): 765 | return not typ.is_string() 766 | 767 | def __call__(self, ctx, typ): 768 | return ustr(repr(ctx.obj)) 769 | 770 | 771 | class ReprStringHandlerInlineRepr(ReprStringHandler): 772 | """repr string object in one line""" 773 | 774 | def rearrenge(self, ctx, typ, wrapper): 775 | shrink_inner_string(ctx, 776 | self.config.string_break_method, 777 | self.config.string_break_width, 778 | wrapper) 779 | return wrapper.wrap(ctx.obj) 780 | 781 | 782 | class ReprOthersHandlerInlineRepr(ReprOthersHandler): 783 | """repr non-string object in one line""" 784 | 785 | def __call__(self, ctx, typ): 786 | ctx.obj = ustr(repr(ctx.obj)) 787 | shrink_inner_string(ctx, 788 | self.config.string_break_method, 789 | self.config.string_break_width, 790 | None) 791 | 792 | return ctx.obj 793 | 794 | 795 | class BlockContent(object): 796 | 797 | def __init__(self, blk): 798 | self.blk = blk 799 | self.str = '' 800 | 801 | def build(self, class_type='', class_name=''): 802 | pass 803 | 804 | def write(self, s): 805 | self.str += s 806 | config = self.blk.config 807 | if config and config.stream: 808 | config.stream.write(s) 809 | config.stream.flush() 810 | return s 811 | 812 | def puts(self, s): 813 | s += '\n' 814 | return self.write(s) 815 | 816 | def count_lines(self): 817 | return self.str.count('\n') 818 | 819 | 820 | class BigBrother(object): 821 | """A leader every block reports to, gets commands from""" 822 | indent_cnt = 0 823 | 824 | @staticmethod 825 | def report(): 826 | """report info to hub""" 827 | pass 828 | 829 | @staticmethod 830 | def register(blk): 831 | """register block to hub""" 832 | pass 833 | -------------------------------------------------------------------------------- /beeprint/models/wrapper.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | 7 | from beeprint import constants as C 8 | from ..utils import pyv 9 | 10 | 11 | class Wrapper(object): 12 | 13 | def get_prefix(self): 14 | pass 15 | 16 | def get_suffix(self): 17 | pass 18 | 19 | def wrap(self, obj): 20 | pass 21 | 22 | 23 | class StringWrapper(Wrapper): 24 | 25 | def __init__(self, config, typ, lqm=None, rqm=None, etm=None): 26 | 27 | self.config = config 28 | # ReprType 29 | self.typ = typ 30 | # left_quotation_mark 31 | self.lqm = lqm or config.str_display_lqm 32 | # right_quotation_mark 33 | self.rqm = rqm or config.str_display_rqm 34 | # encode type mark 35 | self.etm = etm 36 | 37 | if self.etm is None: 38 | typ = self.typ 39 | self.etm = '' 40 | if typ.is_all(typ._UNICODE_): 41 | if self.config.str_display_not_prefix_u: 42 | pass 43 | else: 44 | if pyv == 2: 45 | self.etm = u'u' 46 | else: 47 | pass 48 | elif typ.is_all(typ._BYTES_): 49 | # in py3, printed string will enclose with b'' 50 | if self.config.str_display_not_prefix_b: 51 | pass 52 | else: 53 | if pyv == 2: 54 | pass 55 | else: 56 | self.etm = u'b' 57 | 58 | def wrap(self, obj): 59 | return ''.join([ 60 | self.etm, 61 | self.lqm, 62 | obj, 63 | self.rqm, 64 | ]) 65 | 66 | def get_prefix(self): 67 | return self.etm + self.lqm 68 | 69 | def get_suffix(self): 70 | return self.rqm 71 | -------------------------------------------------------------------------------- /beeprint/printer.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | import sys 7 | import traceback 8 | import types 9 | import inspect 10 | 11 | from io import StringIO 12 | 13 | from .utils import pyv 14 | 15 | if pyv == 2: 16 | # avoid throw [UnicodeEncodeError: 'ascii' codec can't encode characters] 17 | # exceptions, without these lines, the sys.getdefaultencoding() returns ascii 18 | from imp import reload 19 | 20 | reload(sys) 21 | sys.setdefaultencoding('utf-8') 22 | 23 | from . import constants as C 24 | from .utils import print_exc_plus 25 | from .models.block import Block, Context 26 | from .config import Config 27 | from .debug_kit import print_obj_path 28 | 29 | 30 | def pp(o, output=True, max_depth=5, indent=2, width=80, sort_keys=True, string_break_enable=True, 31 | hide_attr_by_prefixes=Config.prop_leading_filters, 32 | config=None, 33 | **kwargs): 34 | """print data beautifully 35 | """ 36 | 37 | if config: 38 | config = config.clone() 39 | else: 40 | config = Config() 41 | 42 | assert max_depth > 0 43 | config.max_depth = max_depth 44 | 45 | assert indent > 0 46 | config.indent_char = u' ' * indent 47 | 48 | assert width >= 0 49 | config.string_break_width = width 50 | 51 | config.prop_leading_filters = hide_attr_by_prefixes 52 | 53 | if string_break_enable is False: 54 | config.string_break_enable = False 55 | config.string_break_method = C._STRING_BREAK_BY_NONE 56 | 57 | config.dict_ordered_key_enable = bool(sort_keys) 58 | for k, v in kwargs.items(): 59 | if getattr(config, k): 60 | setattr(config, k, v) 61 | 62 | if not output: 63 | config.stream = None 64 | 65 | try: 66 | res = str(Block(config, Context(obj=o))) 67 | except: 68 | try: 69 | print_obj_path() 70 | except: 71 | pass 72 | raise 73 | if config.debug_level != 0: 74 | if config.debug_delay: 75 | print(config.debug_stream.getvalue()) 76 | 77 | if not output: 78 | return res 79 | -------------------------------------------------------------------------------- /beeprint/terminal_size.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | copied from https://gist.github.com/jtriley/1108174 4 | """ 5 | from __future__ import print_function 6 | from __future__ import absolute_import 7 | from __future__ import unicode_literals 8 | from __future__ import division 9 | import os 10 | import shlex 11 | import struct 12 | import platform 13 | import subprocess 14 | 15 | 16 | def get_terminal_size(): 17 | """ getTerminalSize() 18 | - get width and height of console 19 | - works on linux,os x,windows,cygwin(windows) 20 | originally retrieved from: 21 | http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python 22 | """ 23 | current_os = platform.system() 24 | tuple_xy = None 25 | if current_os == 'Windows': 26 | tuple_xy = _get_terminal_size_windows() 27 | if tuple_xy is None: 28 | tuple_xy = _get_terminal_size_tput() 29 | # needed for window's python in cygwin's xterm! 30 | if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'): 31 | tuple_xy = _get_terminal_size_linux() 32 | if tuple_xy is None: 33 | tuple_xy = (80, 25) # default value 34 | return tuple_xy 35 | 36 | 37 | def _get_terminal_size_windows(): 38 | try: 39 | from ctypes import windll, create_string_buffer 40 | # stdin handle is -10 41 | # stdout handle is -11 42 | # stderr handle is -12 43 | h = windll.kernel32.GetStdHandle(-12) 44 | csbi = create_string_buffer(22) 45 | res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) 46 | if res: 47 | (bufx, bufy, curx, cury, wattr, 48 | left, top, right, bottom, 49 | maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) 50 | sizex = right - left + 1 51 | sizey = bottom - top + 1 52 | return sizex, sizey 53 | except: 54 | pass 55 | 56 | 57 | def _get_terminal_size_tput(): 58 | # get terminal width 59 | # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window 60 | try: 61 | cols = int(subprocess.check_call(shlex.split('tput cols'))) 62 | rows = int(subprocess.check_call(shlex.split('tput lines'))) 63 | return (cols, rows) 64 | except: 65 | pass 66 | 67 | 68 | def _get_terminal_size_linux(): 69 | def ioctl_GWINSZ(fd): 70 | try: 71 | import fcntl 72 | import termios 73 | cr = struct.unpack('hh', 74 | fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) 75 | return cr 76 | except: 77 | pass 78 | cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) 79 | if not cr: 80 | try: 81 | fd = os.open(os.ctermid(), os.O_RDONLY) 82 | cr = ioctl_GWINSZ(fd) 83 | os.close(fd) 84 | except: 85 | pass 86 | if not cr: 87 | try: 88 | cr = (os.environ['LINES'], os.environ['COLUMNS']) 89 | except: 90 | return None 91 | return int(cr[1]), int(cr[0]) 92 | 93 | if __name__ == "__main__": 94 | sizex, sizey = get_terminal_size() 95 | print('width =', sizex, 'height =', sizey) 96 | -------------------------------------------------------------------------------- /beeprint/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | from __future__ import division 6 | import inspect 7 | import sys 8 | import types 9 | import traceback 10 | 11 | 12 | _unicode = None 13 | if sys.version_info < (3, 0): 14 | pyv = 2 15 | _unicode = unicode 16 | else: 17 | pyv = 3 18 | _unicode = str 19 | 20 | def is_class_method(name, val): 21 | if pyv == 2: 22 | return isinstance(val, types.MethodType) and val.im_self is None 23 | elif pyv == 3: 24 | # in python 2, a class method is unbound method type 25 | # in python 3, a class method is function type as well as a function 26 | raise Exception("python 3 only has function type and bound method type") 27 | 28 | def is_instance_method(name, val): 29 | if pyv == 3: 30 | return inspect.ismethod(val) 31 | elif pyv == 2: 32 | return isinstance(val, types.MethodType) and val.im_self is not None 33 | 34 | def is_pan_function(name, val): 35 | """detect pan-function which including function and bound method in python 3 36 | function and unbound method and bound method in python 2 37 | """ 38 | return inspect.isfunction(val) or inspect.ismethod(val) or inspect.isbuiltin(val) 39 | 40 | def print_exc_plus(): 41 | """ 42 | Print the usual traceback information, followed by a listing of all the 43 | local variables in each frame. 44 | """ 45 | tb = sys.exc_info()[2] 46 | while 1: 47 | if not tb.tb_next: 48 | break 49 | tb = tb.tb_next 50 | stack = [] 51 | f = tb.tb_frame 52 | while f: 53 | stack.append(f) 54 | f = f.f_back 55 | stack.reverse() 56 | traceback.print_exc() 57 | print("Locals by frame, innermost last") 58 | for frame in stack: 59 | print() 60 | print("Frame %s in %s at line %s" % (frame.f_code.co_name, 61 | frame.f_code.co_filename, 62 | frame.f_lineno)) 63 | for key, value in frame.f_locals.items(): 64 | print("\t%20s = " % key, end='') 65 | # We have to be careful not to cause a new error in our error 66 | # printer! Calling str() on an unknown object could cause an 67 | # error we don't want. 68 | try: 69 | print(value) 70 | except: 71 | print("") 72 | 73 | def is_newline_obj(o): 74 | if hasattr(o, '__module__'): 75 | return True 76 | return False 77 | 78 | def is_class_instance(o): 79 | # print('%s: class instance: %s' % (inspect.isclass(o), o)) 80 | if o is None: 81 | return False 82 | try: 83 | # to detect: 84 | # old-style class & new-style class 85 | # instance of old-style class and of new-style class 86 | # method of instance of both class 87 | # function 88 | 89 | # o.__module__ in python 3.5 some instance has no this attribute 90 | 91 | if (inspect.isclass(o) 92 | or inspect.isfunction(o) 93 | or inspect.ismethod(o)): 94 | return False 95 | if isinstance(o, (int, float, list, tuple, dict, str, _unicode)): 96 | return False 97 | return True 98 | except: 99 | pass 100 | return False 101 | 102 | def is_class(o): 103 | # print('%s: class: %s' % (inspect.isclass(o), o)) 104 | return inspect.isclass(o) 105 | 106 | if is_class_instance(o) or inspect.isfunction(o) or inspect.isbuiltin(o) or inspect.ismethod(o): 107 | return False 108 | 109 | return True 110 | 111 | def get_name(o): 112 | if hasattr(o, '__name__'): 113 | return o.__name__ 114 | if hasattr(o, '__class__'): 115 | return o.__class__.__name__ 116 | 117 | raise Exception("%s" % type(o)) 118 | 119 | def has_parent_class(obj, parent_name): 120 | if hasattr(obj, '__mro__'): 121 | mro = obj.__mro__ 122 | elif hasattr(obj, '__class__'): 123 | mro = obj.__class__.__mro__ 124 | else: 125 | return False 126 | 127 | for cls in mro: 128 | cls_name = get_name(cls) 129 | if cls_name.endswith(parent_name): 130 | return True 131 | return False 132 | 133 | 134 | def has_custom_repr(o): 135 | repr_typ_name = lambda o: type(o.__repr__).__name__ 136 | builtin_repr_names = ['method-wrapper', 'wrapper_descriptor', 'method-wrapper'] 137 | return hasattr(o, '__repr__') and (repr_typ_name(o) not in builtin_repr_names) 138 | 139 | 140 | def get_type(o): 141 | if is_class_instance(o): 142 | label = 'instance' 143 | elif inspect.isfunction(o): 144 | label = 'function' 145 | elif inspect.isbuiltin(o): 146 | label = 'builtin' 147 | elif inspect.ismethod(o): 148 | label = 'method' 149 | else: 150 | '本身就是类,不是对象' 151 | label = 'class' 152 | 153 | return label 154 | 155 | 156 | def is_base_type(o): 157 | return isinstance(o, (list, tuple, dict, int, float, str, _unicode)) 158 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/* 2 | _build_html/* 3 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Beeprint.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Beeprint.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Beeprint" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Beeprint" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Beeprint documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Aug 26 17:19:03 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # import os 20 | # import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | # 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # 40 | # source_suffix = ['.rst', '.md'] 41 | source_suffix = '.rst' 42 | 43 | # The encoding of source files. 44 | # 45 | # source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'Beeprint' 52 | copyright = u'2016, PanYangYang' 53 | author = u'PanYangYang' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = u'1.0' 61 | # The full version, including alpha/beta/rc tags. 62 | release = u'1.0' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | # 74 | # today = '' 75 | # 76 | # Else, today_fmt is used as the format for a strftime call. 77 | # 78 | # today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | # This patterns also effect to html_static_path and html_extra_path 83 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 84 | 85 | # The reST default role (used for this markup: `text`) to use for all 86 | # documents. 87 | # 88 | # default_role = None 89 | 90 | # If true, '()' will be appended to :func: etc. cross-reference text. 91 | # 92 | # add_function_parentheses = True 93 | 94 | # If true, the current module name will be prepended to all description 95 | # unit titles (such as .. function::). 96 | # 97 | # add_module_names = True 98 | 99 | # If true, sectionauthor and moduleauthor directives will be shown in the 100 | # output. They are ignored by default. 101 | # 102 | # show_authors = False 103 | 104 | # The name of the Pygments (syntax highlighting) style to use. 105 | pygments_style = 'sphinx' 106 | 107 | # A list of ignored prefixes for module index sorting. 108 | # modindex_common_prefix = [] 109 | 110 | # If true, keep warnings as "system message" paragraphs in the built documents. 111 | # keep_warnings = False 112 | 113 | # If true, `todo` and `todoList` produce output, else they produce nothing. 114 | todo_include_todos = False 115 | 116 | 117 | # -- Options for HTML output ---------------------------------------------- 118 | 119 | # The theme to use for HTML and HTML Help pages. See the documentation for 120 | # a list of builtin themes. 121 | # 122 | html_theme = 'alabaster' 123 | 124 | # Theme options are theme-specific and customize the look and feel of a theme 125 | # further. For a list of options available for each theme, see the 126 | # documentation. 127 | # 128 | # html_theme_options = {} 129 | 130 | # Add any paths that contain custom themes here, relative to this directory. 131 | # html_theme_path = [] 132 | 133 | # The name for this set of Sphinx documents. 134 | # " v documentation" by default. 135 | # 136 | # html_title = u'Beeprint v1.0' 137 | 138 | # A shorter title for the navigation bar. Default is the same as html_title. 139 | # 140 | # html_short_title = None 141 | 142 | # The name of an image file (relative to this directory) to place at the top 143 | # of the sidebar. 144 | # 145 | # html_logo = None 146 | 147 | # The name of an image file (relative to this directory) to use as a favicon of 148 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 149 | # pixels large. 150 | # 151 | # html_favicon = None 152 | 153 | # Add any paths that contain custom static files (such as style sheets) here, 154 | # relative to this directory. They are copied after the builtin static files, 155 | # so a file named "default.css" will overwrite the builtin "default.css". 156 | html_static_path = ['_static'] 157 | 158 | # Add any extra paths that contain custom files (such as robots.txt or 159 | # .htaccess) here, relative to this directory. These files are copied 160 | # directly to the root of the documentation. 161 | # 162 | # html_extra_path = [] 163 | 164 | # If not None, a 'Last updated on:' timestamp is inserted at every page 165 | # bottom, using the given strftime format. 166 | # The empty string is equivalent to '%b %d, %Y'. 167 | # 168 | # html_last_updated_fmt = None 169 | 170 | # If true, SmartyPants will be used to convert quotes and dashes to 171 | # typographically correct entities. 172 | # 173 | # html_use_smartypants = True 174 | 175 | # Custom sidebar templates, maps document names to template names. 176 | # 177 | # html_sidebars = {} 178 | 179 | # Additional templates that should be rendered to pages, maps page names to 180 | # template names. 181 | # 182 | # html_additional_pages = {} 183 | 184 | # If false, no module index is generated. 185 | # 186 | # html_domain_indices = True 187 | 188 | # If false, no index is generated. 189 | # 190 | # html_use_index = True 191 | 192 | # If true, the index is split into individual pages for each letter. 193 | # 194 | # html_split_index = False 195 | 196 | # If true, links to the reST sources are added to the pages. 197 | # 198 | # html_show_sourcelink = True 199 | 200 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 201 | # 202 | # html_show_sphinx = True 203 | 204 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 205 | # 206 | # html_show_copyright = True 207 | 208 | # If true, an OpenSearch description file will be output, and all pages will 209 | # contain a tag referring to it. The value of this option must be the 210 | # base URL from which the finished HTML is served. 211 | # 212 | # html_use_opensearch = '' 213 | 214 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 215 | # html_file_suffix = None 216 | 217 | # Language to be used for generating the HTML full-text search index. 218 | # Sphinx supports the following languages: 219 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 220 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 221 | # 222 | # html_search_language = 'en' 223 | 224 | # A dictionary with options for the search language support, empty by default. 225 | # 'ja' uses this config value. 226 | # 'zh' user can custom change `jieba` dictionary path. 227 | # 228 | # html_search_options = {'type': 'default'} 229 | 230 | # The name of a javascript file (relative to the configuration directory) that 231 | # implements a search results scorer. If empty, the default will be used. 232 | # 233 | # html_search_scorer = 'scorer.js' 234 | 235 | # Output file base name for HTML help builder. 236 | htmlhelp_basename = 'Beeprintdoc' 237 | 238 | # -- Options for LaTeX output --------------------------------------------- 239 | 240 | latex_elements = { 241 | # The paper size ('letterpaper' or 'a4paper'). 242 | # 243 | # 'papersize': 'letterpaper', 244 | 245 | # The font size ('10pt', '11pt' or '12pt'). 246 | # 247 | # 'pointsize': '10pt', 248 | 249 | # Additional stuff for the LaTeX preamble. 250 | # 251 | # 'preamble': '', 252 | 253 | # Latex figure (float) alignment 254 | # 255 | # 'figure_align': 'htbp', 256 | } 257 | 258 | # Grouping the document tree into LaTeX files. List of tuples 259 | # (source start file, target name, title, 260 | # author, documentclass [howto, manual, or own class]). 261 | latex_documents = [ 262 | (master_doc, 'Beeprint.tex', u'Beeprint Documentation', 263 | u'PanYangYang', 'manual'), 264 | ] 265 | 266 | # The name of an image file (relative to this directory) to place at the top of 267 | # the title page. 268 | # 269 | # latex_logo = None 270 | 271 | # For "manual" documents, if this is true, then toplevel headings are parts, 272 | # not chapters. 273 | # 274 | # latex_use_parts = False 275 | 276 | # If true, show page references after internal links. 277 | # 278 | # latex_show_pagerefs = False 279 | 280 | # If true, show URL addresses after external links. 281 | # 282 | # latex_show_urls = False 283 | 284 | # Documents to append as an appendix to all manuals. 285 | # 286 | # latex_appendices = [] 287 | 288 | # It false, will not define \strong, \code, itleref, \crossref ... but only 289 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 290 | # packages. 291 | # 292 | # latex_keep_old_macro_names = True 293 | 294 | # If false, no module index is generated. 295 | # 296 | # latex_domain_indices = True 297 | 298 | 299 | # -- Options for manual page output --------------------------------------- 300 | 301 | # One entry per manual page. List of tuples 302 | # (source start file, name, description, authors, manual section). 303 | man_pages = [ 304 | (master_doc, 'beeprint', u'Beeprint Documentation', 305 | [author], 1) 306 | ] 307 | 308 | # If true, show URL addresses after external links. 309 | # 310 | # man_show_urls = False 311 | 312 | 313 | # -- Options for Texinfo output ------------------------------------------- 314 | 315 | # Grouping the document tree into Texinfo files. List of tuples 316 | # (source start file, target name, title, author, 317 | # dir menu entry, description, category) 318 | texinfo_documents = [ 319 | (master_doc, 'Beeprint', u'Beeprint Documentation', 320 | author, 'Beeprint', 'One line description of project.', 321 | 'Miscellaneous'), 322 | ] 323 | 324 | # Documents to append as an appendix to all manuals. 325 | # 326 | # texinfo_appendices = [] 327 | 328 | # If false, no module index is generated. 329 | # 330 | # texinfo_domain_indices = True 331 | 332 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 333 | # 334 | # texinfo_show_urls = 'footnote' 335 | 336 | # If true, do not generate a @detailmenu in the "Top" node's menu. 337 | # 338 | # texinfo_no_detailmenu = False 339 | -------------------------------------------------------------------------------- /docs/diary.md: -------------------------------------------------------------------------------- 1 | ## 如何实现复杂对象的简洁输出 2 | - 需求: 3 | 1. 避免一次性输出大量内容 4 | 5 | - 方案: 6 | 1. 广度优先,遍历当前层级的所有节点,若不溢出则对下一层级执行相同步骤,溢出则**结束**打印 7 | 1. 深度优先,优先输出当前节点及其子节点,若不溢出则对兄弟节点执行相同步骤,溢出则**打印后**结束 8 | - 难点: 9 | 1. 需要获取 子节点block 的相关信息,比如该 block 会占用多少行 10 | 1. 广度优先时,当前架构为深度优先模式,会沿着对象树一直深入下去,需要有个机制暂停它的深入。 11 | 1. 广度优先时,需要有全局调度,以便实现对同一层级,但不同根节点的遍历。 12 | 1. 广度优先时,需要关闭边遍历边输出功能。 13 | -------------------------------------------------------------------------------- /docs/images/a_real_world_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyanyany/beeprint/54c38876b3c8f2009bb74dec1dffbc5ee8086498/docs/images/a_real_world_example.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Beeprint 2 | ======== 3 | 4 | Beeprint will print variables in a human friendly way. 5 | 6 | Look how easy it is to use:: 7 | 8 | from beeprint.printer import beeprint as pp 9 | pp(some_variables) 10 | 11 | Features 12 | -------- 13 | 14 | - Be awesome 15 | - Make things faster 16 | 17 | Installation 18 | ------------ 19 | 20 | Install beeprint by running:: 21 | 22 | pip install beeprint 23 | 24 | 25 | Contribute 26 | ---------- 27 | 28 | - Issue Tracker: github.com/panyanyany/beeprint/issues 29 | - Source Code: github.com/panyanyany/beeprint 30 | 31 | Support 32 | ------- 33 | 34 | None 35 | 36 | License 37 | ------- 38 | 39 | The project is licensed under the BSD license. 40 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Beeprint.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Beeprint.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | sdist: 2 | python setup.py sdist || true 3 | 4 | edist: 5 | python setup.py bdist_egg || true 6 | 7 | dist: 8 | python setup.py sdist bdist_egg || true 9 | 10 | register: 11 | python setup.py register -r pypi || true 12 | python setup.py register -r pypitest || true 13 | 14 | full: 15 | # TypeError: Retry.__init__() got an unexpected keyword argument 'method_whitelist' 16 | # solved: pip install 'urllib3<2' 17 | python setup.py sdist 18 | twine upload dist/* 19 | # python2.7 setup.py bdist_egg upload 20 | # python3.5 setup.py bdist_egg upload 21 | 22 | test: 23 | python tests/main.py || true 24 | 25 | clean: 26 | rm -rf beeprint.egg-info/ build/ dist/ 27 | -------------------------------------------------------------------------------- /readme.rst: -------------------------------------------------------------------------------- 1 | beeprint 2 | ======== 3 | 4 | make your debug printing more friendly 5 | 6 | Features 7 | ======== 8 | 9 | - **dict:** print elegantly with ordered keys 10 | - **text:** auto break into lines, auto clip long text 11 | - **multi language:** supports English, Chinese 12 | - outstanding mark to class and instance 13 | - supports user-defined `__repr__` 14 | - **recursion** aware 15 | - compatible with py2 py3 16 | 17 | Contents 18 | ======== 19 | 20 | .. contents:: 21 | 22 | Examples 23 | ======== 24 | 25 | A Real World Example 26 | -------------------- 27 | 28 | .. figure:: https://github.com/panyanyany/beeprint/raw/master/docs/images/a_real_world_example.png 29 | :alt: A Real World Example 30 | 31 | 32 | Import beeprint as pp 33 | --------------------- 34 | 35 | :: 36 | 37 | from beeprint import pp 38 | 39 | Print dict 40 | ---------- 41 | 42 | **pprint:** 43 | 44 | :: 45 | 46 | {'entities': {'hashtags': [], 47 | 'urls': [{'display_url': 'tumblr.com/xnr37hf0yz', 48 | 'expanded_url': 'http://tumblr.com/xnr37hf0yz', 49 | 'indices': [107, 126], 50 | 'url': 'http://t.co/cCIWIwg'}], 51 | 'user_mentions': []}} 52 | 53 | **beeprint:** 54 | 55 | :: 56 | 57 | { 58 | 'entities': { 59 | 'hashtags': [], 60 | 'urls': [ 61 | { 62 | 'display_url': 'tumblr.com/xnr37hf0yz', 63 | 'expanded_url': 'http://tumblr.com/xnr37hf0yz', 64 | 'indices': [107, 126], 65 | 'url': 'http://t.co/cCIWIwg', 66 | }, 67 | ], 68 | 'user_mentions': [], 69 | }, 70 | } 71 | 72 | Print class 73 | ----------- 74 | 75 | **pprint:** 76 | 77 | :: 78 | 79 | 80 | 81 | **beeprint:** 82 | 83 | :: 84 | 85 | class(NormalClassNewStyle): 86 | dicts: { 87 | }, 88 | lists: [], 89 | static_props: 1, 90 | tupl: (1, 2) 91 | 92 | Print instance 93 | -------------- 94 | 95 | **pprint:** 96 | 97 | :: 98 | 99 | 100 | 101 | **beeprint:** 102 | 103 | :: 104 | 105 | instance(NormalClassNewStyle): 106 | dicts: { 107 | }, 108 | lists: [], 109 | say_hi: 'hello world', 110 | static_props: 1, 111 | tupl: (1, 2) 112 | 113 | Print long text 114 | --------------- 115 | 116 | **pprint:** 117 | 118 | :: 119 | 120 | [['\nThe sky and the earth were at first one blurred entity like an egg. Pangu was born into it.\n \n\tThe separation of the sky and the earth took eighteen thousand years-the yang which was light and pure rose to become the sky, \tand the yin which was heavy and murky\xef\xbc\x88\xe6\x9c\xa6\xe8\x83\xa7\xe7\x9a\x84\xef\xbc\x89 sank to form the earth. Between them was Pangu, who went through nine \tchanges every day, his wisdom greater than that of the sky and his ability greater than that of the earth. Every day the sky rose ten feet higher, the earth became ten feet thicker, and Pangu grew ten feet taller.\n \nAnother eighteen thousand years passed, and there was an extremely high sky, an extremely thick earth, and an extremely tall Pangu. After Pangu died, his head turned into the Five Sacred Mountains (Mount Tai, Mount Heng, Mount Hua, Mount Heng, Mount Song), his eyes turned into the moon and the sun, his blood changed into water in river and sea, his hair into grass.\n \nIn all, the universe and Pangu combine in one.\n', 121 | '\n\xe6\x8d\xae\xe6\xb0\x91\xe9\x97\xb4\xe7\xa5\x9e\xe8\xaf\x9d\xe4\xbc\xa0\xe8\xaf\xb4\xe5\x8f\xa4\xe6\x97\xb6\xe7\x9b\x98\xe5\x8f\xa4\xe7\x94\x9f\xe5\x9c\xa8\xe9\xbb\x91\xe6\x9a\x97\xe5\x9b\xa2\xe4\xb8\xad\xef\xbc\x8c\xe4\xbb\x96\xe4\xb8\x8d\xe8\x83\xbd\xe5\xbf\x8d\xe5\x8f\x97\xe9\xbb\x91\xe6\x9a\x97\xef\xbc\x8c\xe7\x94\xa8\xe7\xa5\x9e\xe6\x96\xa7\xe5\x8a\x88\xe5\x90\x91\xe5\x9b\x9b\xe6\x96\xb9\xef\xbc\x8c\xe9\x80\x90\xe6\xb8\x90\xe4\xbd\xbf\xe5\xa4\xa9\xe7\xa9\xba\xe9\xab\x98\xe8\xbf\x9c\xef\xbc\x8c\xe5\xa4\xa7\xe5\x9c\xb0\xe8\xbe\xbd\xe9\x98\x94\xe3\x80\x82\n\t\xe4\xbb\x96\xe4\xb8\xba\xe4\xb8\x8d\xe4\xbd\xbf\xe5\xa4\xa9\xe5\x9c\xb0\xe4\xbc\x9a\xe9\x87\x8d\xe6\x96\xb0\xe5\x90\x88\xe5\xb9\xb6\xef\xbc\x8c\xe7\xbb\xa7\xe7\xbb\xad\xe6\x96\xbd\xe5\xb1\x95\xe6\xb3\x95\xe6\x9c\xaf\xe3\x80\x82\xe6\xaf\x8f\xe5\xbd\x93\xe7\x9b\x98\xe5\x8f\xa4\xe7\x9a\x84\xe8\xba\xab\xe4\xbd\x93\xe9\x95\xbf\xe9\xab\x98\xe4\xb8\x80\xe5\xb0\xba\xef\xbc\x8c\xe5\xa4\xa9\xe7\xa9\xba\xe5\xb0\xb1\xe9\x9a\x8f\xe4\xb9\x8b\xe5\xa2\x9e\xe9\xab\x98\xe4\xb8\x80\xe5\xb0\xba\xef\xbc\x8c\n\t\xe7\xbb\x8f\xe8\xbf\x871.8\xe4\xb8\x87\xe5\xa4\x9a\xe5\xb9\xb4\xe7\x9a\x84\xe5\x8a\xaa\xe5\x8a\x9b\xef\xbc\x8c\xe7\x9b\x98\xe5\x8f\xa4\xe5\x8f\x98\xe6\x88\x90\xe4\xb8\x80\xe4\xbd\x8d\xe9\xa1\xb6\xe5\xa4\xa9\xe7\xab\x8b\xe5\x9c\xb0\xe7\x9a\x84\xe5\xb7\xa8\xe4\xba\xba\xef\xbc\x8c\xe8\x80\x8c\xe5\xa4\xa9\xe7\xa9\xba\xe4\xb9\x9f\xe5\x8d\x87\xe5\xbe\x97\xe9\xab\x98\xe4\xb8\x8d\xe5\x8f\xaf\xe5\x8f\x8a\xef\xbc\x8c\xe5\xa4\xa7\xe5\x9c\xb0\xe4\xb9\x9f\xe5\x8f\x98\xe5\xbe\x97\xe5\x8e\x9a\xe5\xae\x9e\xe6\x97\xa0\xe6\xaf\x94\xe3\x80\x82\xe7\x9b\x98\xe5\x8f\xa4\xe7\x94\x9f\xe5\x89\x8d\xe5\xae\x8c\xe6\x88\x90\xe5\xbc\x80\xe5\xa4\xa9\xe8\xbe\x9f\xe5\x9c\xb0\xe7\x9a\x84\xe4\xbc\x9f\xe5\xa4\xa7\xe4\xb8\x9a\xe7\xbb\xa9\xef\xbc\x8c\xe6\xad\xbb\xe5\x90\x8e\xe6\xb0\xb8\xe8\xbf\x9c\xe7\x95\x99\xe7\xbb\x99\xe5\x90\x8e\xe4\xba\xba\xe6\x97\xa0\xe7\xa9\xb7\xe6\x97\xa0\xe5\xb0\xbd\xe7\x9a\x84\xe5\xae\x9d\xe8\x97\x8f\xef\xbc\x8c\xe6\x88\x90\xe4\xb8\xba\xe4\xb8\xad\xe5\x8d\x8e\xe6\xb0\x91\xe6\x97\x8f\xe5\xb4\x87\xe6\x8b\x9c\xe7\x9a\x84\xe8\x8b\xb1\xe9\x9b\x84\xe3\x80\x82\n']] 122 | 123 | **beeprint:** 124 | 125 | :: 126 | 127 | [ 128 | [ 129 | '\nThe sky and the earth were at first one blurred entity like an egg. Pangu 130 | was born into it.\n \n\tThe separation of the sky and the earth took 131 | ...(12 hidden lines)', 132 | '\n据民间神话传说古时盘古生在黑暗团中,他不能忍受黑暗,用神斧劈向四方,逐渐 133 | 使天空高远,大地辽阔。\n\t他为不使天地会重新合并,继续施展法术。每当盘古的 134 | ...(3 hidden lines)', 135 | ], 136 | ] 137 | 138 | Installation 139 | ============ 140 | 141 | .. code:: shell 142 | 143 | pip install beeprint 144 | 145 | Settings 146 | ======== 147 | 148 | more on `config.py <./beeprint/config.py>`__ 149 | 150 | Others 151 | ====== 152 | `MySQLDesk `_ 153 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pprintpp==0.3.0 2 | urwid==2.1.2 3 | twine==1.8.1 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyanyany/beeprint/54c38876b3c8f2009bb74dec1dffbc5ee8086498/setup.cfg -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | setup( 3 | name = 'beeprint', 4 | packages = ['beeprint', 'beeprint.models', 'beeprint.helpers', 'beeprint.lib'], # this must be the same as the name above 5 | version = '2.4.11', 6 | description = 'make your debug printing more friendly', 7 | author = 'Yangyang Pan', 8 | author_email = '568397440@qq.com', 9 | url = 'https://github.com/panyanyany/beeprint', # use the URL to the github repo 10 | download_url = 'https://github.com/panyanyany/beeprint/archive/master.zip', # I'll explain this in a second 11 | keywords = ['print', 'pprint', 'format', 'debug'], # arbitrary keywords 12 | classifiers = [ 13 | "Programming Language :: Python", 14 | # "Programming Language :: Python :: 2", 15 | "Programming Language :: Python :: 3", 16 | "License :: OSI Approved :: BSD License", 17 | "Operating System :: OS Independent", 18 | 19 | "Environment :: Console", 20 | "Development Status :: 4 - Beta", 21 | "Intended Audience :: Developers", 22 | "Topic :: Software Development", 23 | "Topic :: Utilities", 24 | ], 25 | install_requires = [ 26 | 'urwid', 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyanyany/beeprint/54c38876b3c8f2009bb74dec1dffbc5ee8086498/tests/__init__.py -------------------------------------------------------------------------------- /tests/cmp_to_pprint.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | 5 | from imp import reload 6 | 7 | import unittest 8 | import os 9 | import sys 10 | import types 11 | import inspect 12 | import codecs 13 | 14 | 15 | CUR_SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) 16 | BEEPRINT_PATH = os.path.abspath(os.path.join(CUR_SCRIPT_PATH, '..')) 17 | sys.path.append(BEEPRINT_PATH) 18 | 19 | if sys.version_info < (3, 0): 20 | # avoid throw [UnicodeEncodeError: 'ascii' codec can't encode characters] 21 | # exceptions, without these lines, the sys.getdefaultencoding() returns ascii 22 | reload(sys) 23 | sys.setdefaultencoding('utf-8') 24 | 25 | pyv = 2 26 | else: 27 | unicode = str 28 | pyv = 3 29 | 30 | from beeprint import beeprint as pp, pyv 31 | from beeprint import settings as S 32 | from beeprint import constants as C 33 | from pprint import pprint 34 | from pprintpp import pprint as ppp 35 | 36 | try: 37 | from . import definition as df 38 | except: 39 | import definition as df 40 | 41 | 42 | def pair_print(title, v): 43 | print(title) 44 | print("----") 45 | print("**pprint:**\n```python") 46 | pprint(v) 47 | print("```\n") 48 | print("**beeprint:**\n```python") 49 | pp(v) 50 | print("```\n") 51 | print("**pprintpp:**\n```python") 52 | ppp(v) 53 | print("```\n") 54 | 55 | 56 | def main(): 57 | if len(sys.argv) == 1: 58 | 59 | S.text_wrap_method = C._TEXT_WRAP_BY_WIDTH 60 | pair_print("Print dict", df.dict_multi_keys) 61 | pair_print("Print class", df.NormalClassNewStyle) 62 | pair_print("Print instance", df.inst_of_normal_class_new_style) 63 | pair_print("Print long text", df.long_text_in_list) 64 | return 65 | 66 | for i in range(1, len(sys.argv)): 67 | fn = sys.argv[i] 68 | args[fn]() 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /tests/data/all_in_one.txt: -------------------------------------------------------------------------------- 1 | [ 2 | 1, 3 | 1.1, 4 | 'literal', 5 | 'unicode', 6 | 'literal中文', 7 | 'unicode中文', 8 | [1, 2, 3, 4, 5, 6], 9 | [ 10 | 1, 11 | [2], 12 | { 13 | 'key': 'val', 14 | }, 15 | ], 16 | (1, 2), 17 | function(EmptyFunc), 18 | class(EmptyClassOldStyle), 19 | class(EmptyClassNewStyle), 20 | class(NormalClassOldStyle): 21 | static_props: 1 22 | class(NormalClassNewStyle): 23 | dicts: { 24 | }, 25 | lists: [], 26 | static_props: 1, 27 | tupl: (1, 2) 28 | instance(NormalClassOldStyle): 29 | static_props: 1 30 | instance(NormalClassNewStyle): 31 | dicts: { 32 | }, 33 | lists: [], 34 | say_hi: 'hello world', 35 | static_props: 1, 36 | tupl: (1, 2) 37 | method(mth), 38 | method(mth), 39 | { 40 | 'key': [], 41 | }, 42 | { 43 | 'key': instance(NormalClassNewStyle): 44 | dicts: { 45 | }, 46 | lists: [], 47 | say_hi: 'hello world', 48 | static_props: 1, 49 | tupl: (1, 2) 50 | }, 51 | ] 52 | [ 53 | { 54 | 'english version': '\nThe sky and the earth were at first one blurred entity 55 | like an egg. Pangu was born into it.\n \n\tThe 56 | separation of the sky and the earth took eighteen 57 | thousand years-the yang which was light and pure rose to 58 | become the sky, \tand the yin which was heavy and murky 59 | (朦胧的) sank to form the earth. Between them was 60 | Pangu, who went through nine \tchanges every day, his 61 | wisdom greater than that of the sky and his ability 62 | greater than that of the earth. Every day the sky rose 63 | ten feet higher, the earth became ten feet thicker, and 64 | Pangu grew ten feet taller.\n \nAnother eighteen 65 | thousand years passed, and there was an extremely high 66 | sky, an extremely thick earth, and an extremely tall 67 | Pangu. After Pangu died, his head turned into the Five 68 | Sacred Mountains (Mount Tai, Mount Heng, Mount Hua, 69 | Mount Heng, Mount Song), his eyes turned into the moon 70 | and the sun, his blood changed into water in river and 71 | sea, his hair into grass.\n \nIn all, the universe and 72 | Pangu combine in one.\n', 73 | }, 74 | { 75 | 'simplify chinese versino': '\n据民间神话传说古时盘古生在黑暗团中,他不能忍 76 | 受黑暗,用神斧劈向四方,逐渐使天空高远,大地辽 77 | 阔。\n\t他为不使天地会重新合并,继续施展法术。 78 | 每当盘古的身体长高一尺,天空就随之增高一尺, 79 | \n\t经过1.8万多年的努力,盘古变成一位顶天立地的 80 | 巨人,而天空也升得高不可及,大地也变得厚实无比 81 | 。盘古生前完成开天辟地的伟大业绩,死后永远留给 82 | 后人无穷无尽的宝藏,成为中华民族崇拜的英雄。 83 | \n', 84 | }, 85 | ] 86 | [ 87 | [ 88 | '\nThe sky and the earth were at first one blurred entity like an egg. Pangu 89 | was born into it.\n \n\tThe separation of the sky and the earth took 90 | eighteen thousand years-the yang which was light and pure rose to become 91 | the sky, \tand the yin which was heavy and murky(朦胧的) sank to form the 92 | earth. Between them was Pangu, who went through nine \tchanges every day, 93 | his wisdom greater than that of the sky and his ability greater than that 94 | of the earth. Every day the sky rose ten feet higher, the earth became ten 95 | feet thicker, and Pangu grew ten feet taller.\n \nAnother eighteen thousand 96 | years passed, and there was an extremely high sky, an extremely thick 97 | earth, and an extremely tall Pangu. After Pangu died, his head turned into 98 | the Five Sacred Mountains (Mount Tai, Mount Heng, Mount Hua, Mount Heng, 99 | Mount Song), his eyes turned into the moon and the sun, his blood changed 100 | into water in river and sea, his hair into grass.\n \nIn all, the universe 101 | and Pangu combine in one.\n', 102 | '\n据民间神话传说古时盘古生在黑暗团中,他不能忍受黑暗,用神斧劈向四方,逐渐 103 | 使天空高远,大地辽阔。\n\t他为不使天地会重新合并,继续施展法术。每当盘古的 104 | 身体长高一尺,天空就随之增高一尺,\n\t经过1.8万多年的努力,盘古变成一位顶天 105 | 立地的巨人,而天空也升得高不可及,大地也变得厚实无比。盘古生前完成开天辟地 106 | 的伟大业绩,死后永远留给后人无穷无尽的宝藏,成为中华民族崇拜的英雄。\n', 107 | ], 108 | ] 109 | -------------------------------------------------------------------------------- /tests/data/auto_clip/by_lines.txt: -------------------------------------------------------------------------------- 1 | [ 2 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 3 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 4 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 5 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 6 | ...(1 hidden line)', 7 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 8 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 9 | ...(2 hidden lines)', 10 | ] 11 | -------------------------------------------------------------------------------- /tests/data/auto_clip/no_room.txt: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | '...(len=80)', 4 | {}, 5 | ], 6 | { 7 | 'akey': '...(len=15)', 8 | }, 9 | class(ForNoRoom): 10 | xxxxxxxxxxxxxxxxxxx: '...(len=23)' 11 | ] 12 | -------------------------------------------------------------------------------- /tests/data/class/all_repr_disable.txt: -------------------------------------------------------------------------------- 1 | [ 2 | class(ReprMethodClassOldStyle), 3 | class(ReprMethodClassNewStyle), 4 | class(ReprStaticClassOldStyle), 5 | class(ReprStaticClassNewStyle), 6 | class(ReprClassMethodClassOldStyle), 7 | class(ReprClassMethodClassNewStyle), 8 | class(ReprLambdaClassOldStyle), 9 | class(ReprLambdaClassNewStyle), 10 | function(EmptyFunc), 11 | class(EmptyClassOldStyle), 12 | class(EmptyClassNewStyle), 13 | class(NormalClassOldStyle): 14 | static_props: 1 15 | class(NormalClassNewStyle): 16 | dicts: { 17 | }, 18 | lists: [], 19 | static_props: 1, 20 | tupl: (1, 2) 21 | ] 22 | -------------------------------------------------------------------------------- /tests/data/class/inst_repr_enable.txt: -------------------------------------------------------------------------------- 1 | [ 2 | class(ReprMethodClassOldStyle), 3 | class(ReprMethodClassNewStyle), 4 | class(ReprStaticClassOldStyle), 5 | class(ReprStaticClassNewStyle), 6 | class(ReprClassMethodClassOldStyle), 7 | class(ReprClassMethodClassNewStyle), 8 | class(ReprLambdaClassOldStyle), 9 | class(ReprLambdaClassNewStyle), 10 | function(EmptyFunc), 11 | class(EmptyClassOldStyle), 12 | class(EmptyClassNewStyle), 13 | class(NormalClassOldStyle): 14 | static_props: 1 15 | class(NormalClassNewStyle): 16 | dicts: { 17 | }, 18 | lists: [], 19 | static_props: 1, 20 | tupl: (1, 2) 21 | , 22 | , 23 | , 24 | , 25 | , 26 | , 27 | , 28 | , 29 | None, 30 | instance(EmptyClassOldStyle), 31 | instance(EmptyClassNewStyle), 32 | instance(NormalClassOldStyle): 33 | static_props: 1 34 | instance(NormalClassNewStyle): 35 | dicts: { 36 | }, 37 | lists: [], 38 | say_hi: 'hello world', 39 | static_props: 1, 40 | tupl: (1, 2) 41 | ] 42 | -------------------------------------------------------------------------------- /tests/data/class/last_el.txt: -------------------------------------------------------------------------------- 1 | [ 2 | instance(ReprMethodClassNewStyle), 3 | instance(NormalClassNewStyle): 4 | dicts: { 5 | }, 6 | lists: [], 7 | say_hi: 'hello world', 8 | static_props: 1, 9 | tupl: (1, 2) 10 | instance(ReprMethodClassNewStyle), 11 | ] 12 | -------------------------------------------------------------------------------- /tests/data/dict/comma.txt: -------------------------------------------------------------------------------- 1 | { 2 | 'key': 'aaa,,,', 3 | } 4 | -------------------------------------------------------------------------------- /tests/data/dict/foo.txt: -------------------------------------------------------------------------------- 1 | { 2 | 'entities': { 3 | 'hashtags': [], 4 | 'urls': [ 5 | { 6 | 'display_url': 'tumblr.com/xnr37hf0yz', 7 | 'expanded_url': 'http://tumblr.com/xnr37hf0yz', 8 | 'indices': [107, 126], 9 | 'url': 'http://t.co/cCIWIwg', 10 | }, 11 | ], 12 | 'user_mentions': [], 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /tests/data/inline_repr/out_of_range.txt: -------------------------------------------------------------------------------- 1 | [ 2 | 'this string fits 80 col...(len=75)', 3 | , 3 | 'two': , 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/out_of_range_py2.txt: -------------------------------------------------------------------------------- 1 | [ 2 | 'nomarl\t\n1', 3 | [1, [2], {'key': 'val'}], 4 | , 5 | , 6 | , 7 | , 8 | , 9 | ] 10 | -------------------------------------------------------------------------------- /tests/data/out_of_range_py3.txt: -------------------------------------------------------------------------------- 1 | [ 2 | 'nomarl\t\n1', 3 | [1, [2], {'key': 'val'}], 4 | , 5 | , 6 | , 7 | , 8 | , 9 | ] 10 | -------------------------------------------------------------------------------- /tests/data/recursion.txt: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | , 4 | ], 5 | [ 6 | , 7 | ], 8 | { 9 | 'd': , 10 | }, 11 | { 12 | '1': 1, 13 | 'r': { 14 | 'd2': , 15 | }, 16 | }, 17 | instance(RecurTestNormalClass): 18 | k1: 1, 19 | k2: 20 | class(RecurTestRecurClass): 21 | k1: 1, 22 | k2: 23 | ] 24 | -------------------------------------------------------------------------------- /tests/data/string_break/boundary_break.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | }, 4 | 'boundary testing string: 80col,new line part filled with x ................', 5 | 'boundary testing string: 81col,new line part filled with x ............... 6 | x', 7 | 'boundary testing string: 82col,new line part filled with x ............... 8 | xx', 9 | ] 10 | -------------------------------------------------------------------------------- /tests/data/tuple/nested.txt: -------------------------------------------------------------------------------- 1 | ( 2 | 1, 3 | (2), 4 | ) 5 | -------------------------------------------------------------------------------- /tests/data/tuple/simple.txt: -------------------------------------------------------------------------------- 1 | ( 2 | 1, 3 | { 4 | 'indict': (1), 5 | 'z': 1, 6 | }, 7 | ) 8 | -------------------------------------------------------------------------------- /tests/definition.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from beeprint import constants as C 4 | from beeprint import pp 5 | from beeprint import Config 6 | from beeprint.helper import ustr 7 | from pprintpp import pprint as ppp 8 | 9 | def EmptyFunc(): pass 10 | 11 | class EmptyClassOldStyle: pass 12 | class EmptyClassNewStyle(object): pass 13 | 14 | class NormalClassOldStyle: 15 | def mth():pass 16 | static_props = 1 17 | 18 | class NormalClassNewStyle(object): 19 | def __init__(self): 20 | self.say_hi = 'hello world' 21 | def mth():pass 22 | static_props = 1 23 | lists = [] 24 | dicts = {} 25 | tupl = (1,2) 26 | 27 | inst_of_normal_class_old_style = NormalClassOldStyle() 28 | inst_of_normal_class_new_style = NormalClassNewStyle() 29 | 30 | # there are tabs here!! 31 | long_text_en = """ 32 | The sky and the earth were at first one blurred entity like an egg. Pangu was born into it. 33 | 34 | The separation of the sky and the earth took eighteen thousand years-the yang which was light and pure rose to become the sky, and the yin which was heavy and murky(朦胧的) sank to form the earth. Between them was Pangu, who went through nine changes every day, his wisdom greater than that of the sky and his ability greater than that of the earth. Every day the sky rose ten feet higher, the earth became ten feet thicker, and Pangu grew ten feet taller. 35 | 36 | Another eighteen thousand years passed, and there was an extremely high sky, an extremely thick earth, and an extremely tall Pangu. After Pangu died, his head turned into the Five Sacred Mountains (Mount Tai, Mount Heng, Mount Hua, Mount Heng, Mount Song), his eyes turned into the moon and the sun, his blood changed into water in river and sea, his hair into grass. 37 | 38 | In all, the universe and Pangu combine in one. 39 | """ 40 | 41 | # there are tabs here!! 42 | long_text_cn = """ 43 | 据民间神话传说古时盘古生在黑暗团中,他不能忍受黑暗,用神斧劈向四方,逐渐使天空高远,大地辽阔。 44 | 他为不使天地会重新合并,继续施展法术。每当盘古的身体长高一尺,天空就随之增高一尺, 45 | 经过1.8万多年的努力,盘古变成一位顶天立地的巨人,而天空也升得高不可及,大地也变得厚实无比。盘古生前完成开天辟地的伟大业绩,死后永远留给后人无穷无尽的宝藏,成为中华民族崇拜的英雄。 46 | """ 47 | 48 | long_text_in_list = [ 49 | [ 50 | long_text_en, 51 | long_text_cn, 52 | ], 53 | ] 54 | 55 | long_text_in_dict = [ 56 | {"english version": long_text_en}, 57 | {"simplify chinese versino": long_text_cn}, 58 | ] 59 | 60 | short_list = [1, 2, 3, 4, 5, 6] 61 | complicated_list = [ 62 | 1, 63 | [2, ], 64 | { 65 | 'key': 'val', 66 | }, 67 | ] 68 | 69 | values = [ 70 | 1, 71 | 1.1, 72 | "literal", 73 | u"unicode", 74 | "literal中文", 75 | u"unicode中文", 76 | short_list, 77 | complicated_list, 78 | (1,2), 79 | EmptyFunc, 80 | EmptyClassOldStyle, 81 | EmptyClassNewStyle, 82 | NormalClassOldStyle, 83 | NormalClassNewStyle, 84 | inst_of_normal_class_old_style, 85 | inst_of_normal_class_new_style, 86 | inst_of_normal_class_old_style.mth, 87 | inst_of_normal_class_new_style.mth, 88 | { 89 | 'key': [], 90 | }, 91 | { 92 | 'key': inst_of_normal_class_new_style, 93 | }, 94 | ] 95 | 96 | v_line_break_boundary = [ 97 | {}, 98 | "boundary testing string: 80col,new line part filled with x ................", 99 | "boundary testing string: 81col,new line part filled with x ............... x", 100 | "boundary testing string: 82col,new line part filled with x ............... xx", 101 | ] 102 | 103 | def test_boundary_break(output=True): 104 | return pp(v_line_break_boundary, output, string_break_method=C._STRING_BREAK_BY_WIDTH) 105 | 106 | 107 | out_of_range = [ 108 | 'nomarl\t\n1', 109 | complicated_list, 110 | EmptyFunc, 111 | EmptyClassOldStyle, 112 | EmptyClassNewStyle, 113 | inst_of_normal_class_old_style, 114 | inst_of_normal_class_new_style, 115 | ] 116 | 117 | def test_out_of_range(output=True): 118 | return pp(out_of_range, output, max_depth=1) 119 | 120 | out_of_range_in_dict = { 121 | 'one': inst_of_normal_class_new_style, 122 | 'two': inst_of_normal_class_new_style, 123 | } 124 | 125 | def test_out_of_range_in_dict(output=True): 126 | return pp(out_of_range_in_dict, output, max_depth=1) 127 | 128 | clip_by_3_lines = [ 129 | 'a'*(77*2 - 2), 130 | 'a'*77*2, 131 | 'a'*77*3, 132 | ] 133 | 134 | 135 | def test_3lines_clip(output=True): 136 | config = Config() 137 | config.text_autoclip_enable = True 138 | config.text_autoclip_method = C._TEXT_AUTOCLIP_BY_LINE 139 | config.string_break_enable = True 140 | config.string_break_method = C._STRING_BREAK_BY_WIDTH 141 | return pp(clip_by_3_lines, output, config=config) 142 | 143 | 144 | dict_multi_keys = { 145 | 'entities': { 146 | 'hashtags': [], 147 | 'urls': [ 148 | { 149 | 'display_url': 'tumblr.com/xnr37hf0yz', 150 | 'expanded_url': 'http://tumblr.com/xnr37hf0yz', 151 | 'indices': [107, 126], 152 | 'url': 'http://t.co/cCIWIwg' 153 | } 154 | ], 155 | 'user_mentions': [] 156 | }, 157 | } 158 | 159 | 160 | def test_dict_ordered_keys(output=True): 161 | return pp(dict_multi_keys, output, dict_ordered_key_enable=True) 162 | 163 | 164 | def test_complicate_data(output=True): 165 | config = Config() 166 | config.string_break_method = C._STRING_BREAK_BY_WIDTH 167 | config.text_autoclip_enable = False 168 | res = pp(values, output, config=config) 169 | res += pp(long_text_in_dict, output, config=config) 170 | res += pp(long_text_in_list, output, config=config) 171 | return res 172 | 173 | inline_repr = [ 174 | "this string fits 80 col line ..... ..... ..... ..... ..... ..... ..... ....", 175 | inst_of_normal_class_new_style, 176 | inst_of_normal_class_new_style.mth, 177 | ] 178 | 179 | def test_inline_repr_out_of_range(output=True): 180 | config = Config() 181 | config.max_depth = 1 182 | config.string_break_method = C._STRING_BREAK_BY_WIDTH 183 | config.string_break_width = 40 184 | return pp(inline_repr, output, config=config) 185 | 186 | tuple_testing = (1, {'indict':(1,), 'z': 1},) 187 | 188 | def test_tuple(output=True): 189 | config = Config() 190 | return pp(tuple_testing, output, config=config) 191 | 192 | tuple_nested = (1, (2,)) 193 | def test_tuple_nested(output=True): 194 | config = Config() 195 | return pp(tuple_nested, output, config=config) 196 | 197 | def test_class(output=True): 198 | config = Config() 199 | return pp(EmptyClassNewStyle, output, config=config) 200 | 201 | sort_of_string = [ 202 | '\\', 203 | u'\\', 204 | b'\\', 205 | u'\\'.encode('utf8'), 206 | ] 207 | sort_of_string += [ 208 | 'normal', 209 | u'unicode', 210 | ] 211 | sort_of_string += [ 212 | '中文', 213 | # b'中文', 214 | u'中文', 215 | u'中文'.encode('utf8'), 216 | ] 217 | sort_of_string += [ 218 | '\xff\xfe', 219 | b'\xff\xfe', 220 | u'\xff\xfe', 221 | u'\xff\xfe'.encode('utf8'), 222 | ] 223 | sort_of_string += [ 224 | '\ud800', # different in py2 and py3 225 | b'\ud800', 226 | u'\ud800', 227 | # u'\ud800-\udbff\\\udc00\udc01-\udfff', 228 | ] 229 | 230 | def test_sort_of_string(output=True): 231 | config = Config() 232 | config.debug_delay = False 233 | config.str_display_not_prefix_u = False 234 | config.str_display_not_prefix_b = False 235 | for sstr in sort_of_string: 236 | ints = '' 237 | for e in sstr: 238 | try: 239 | ints += '%d ' % ord(e) 240 | except: 241 | ints += '%d ' % e 242 | print('%40s %s %s' % (ints, repr(sstr), len(sstr))) 243 | return pp(sort_of_string, output, config=config) 244 | # return ppp(sort_of_string) 245 | 246 | 247 | # >> ReprMethod 248 | class ReprMethodClassOldStyle: 249 | def __repr__(self): 250 | return "" % self.__class__.__name__ 251 | 252 | class ReprMethodClassNewStyle(object): 253 | def __repr__(self): 254 | return "" % self.__class__.__name__ 255 | 256 | # >> ReprStatic 257 | class ReprStaticClassOldStyle: 258 | @staticmethod 259 | def __repr__(): 260 | return "" 261 | 262 | class ReprStaticClassNewStyle(object): 263 | @staticmethod 264 | def __repr__(): 265 | return "" 266 | 267 | # >> ReprClassMethod 268 | class ReprClassMethodClassOldStyle: 269 | @classmethod 270 | def __repr__(cls): 271 | return "" % cls.__name__ 272 | 273 | class ReprClassMethodClassNewStyle(object): 274 | @classmethod 275 | def __repr__(cls): 276 | return "" % cls.__name__ 277 | 278 | # >> ReprLambda 279 | class ReprLambdaClassOldStyle: 280 | __repr__ = lambda self: "" % self.__class__.__name__ 281 | 282 | class ReprLambdaClassNewStyle: 283 | __repr__ = lambda self: "" % self.__class__.__name__ 284 | 285 | def test_class_last_el(output=True): 286 | config = Config() 287 | config.instance_repr_enable = False 288 | rm = ReprMethodClassNewStyle() 289 | nc = NormalClassNewStyle() 290 | return pp([rm, nc, rm], output, config=config) 291 | 292 | 293 | class OuterClass(object): 294 | class InnerClass(object): 295 | pass 296 | 297 | 298 | def test_inner_class(output=True): 299 | config = Config() 300 | return pp(OuterClass, output, config=config) 301 | 302 | 303 | class ForNoRoom(object): 304 | xxxxxxxxxxxxxxxxxxx = 'ooooooooooooooooooooooo' 305 | 306 | autoclip_no_room = [ 307 | ['.'*80, {}], 308 | {'akey': 'value'*3}, 309 | ForNoRoom, 310 | ] 311 | 312 | def test_autoclip_no_room(output=True): 313 | config = Config() 314 | # config.debug_level = 9 315 | config.max_depth = 2 316 | config.string_break_width = 1 317 | config.string_break_method = C._STRING_BREAK_BY_WIDTH 318 | return pp(autoclip_no_room, output, config=config) 319 | 320 | 321 | class_repr = [ 322 | ReprMethodClassOldStyle, 323 | ReprMethodClassNewStyle, 324 | ReprStaticClassOldStyle, 325 | ReprStaticClassNewStyle, 326 | ReprClassMethodClassOldStyle, 327 | ReprClassMethodClassNewStyle, 328 | ReprLambdaClassOldStyle, 329 | ReprLambdaClassNewStyle, 330 | EmptyFunc, 331 | EmptyClassOldStyle, 332 | EmptyClassNewStyle, 333 | NormalClassOldStyle, 334 | NormalClassNewStyle, 335 | ] 336 | def test_class_all_repr_disable(output=True): 337 | return pp(class_repr, output, instance_repr_enable=False) 338 | 339 | def test_class_inst_repr_enable(output=True): 340 | inst_repr = [] 341 | for c in class_repr: 342 | inst_repr.append(c()) 343 | return pp(class_repr + inst_repr, output) 344 | 345 | 346 | class RecurTestNormalClass(object): 347 | k1 = 1 348 | 349 | inst_of_recur_normal = RecurTestNormalClass() 350 | inst_of_recur_normal.k2 = inst_of_recur_normal 351 | 352 | class RecurTestRecurClass(object): 353 | k1 = 1 354 | RecurTestRecurClass.k2 = RecurTestRecurClass 355 | 356 | def test_recursion(output=True): 357 | d = {} 358 | d['d'] = d 359 | 360 | d2 = {'1':1} 361 | d2['r'] = {'d2':d2} 362 | 363 | l = [] 364 | l.append(l) 365 | 366 | recursive_values = [ 367 | l, 368 | l, # this one should not be treat as recursion 369 | d, 370 | d2, 371 | inst_of_recur_normal, 372 | RecurTestRecurClass, 373 | ] 374 | return pp(recursive_values, output) 375 | # return ppp(recursive_values) 376 | -------------------------------------------------------------------------------- /tests/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import unittest 4 | import os 5 | import sys 6 | import codecs 7 | import re 8 | 9 | CUR_SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) 10 | BEEPRINT_PATH = os.path.abspath(os.path.join(CUR_SCRIPT_PATH, '..')) 11 | sys.path.insert(0, BEEPRINT_PATH) 12 | 13 | from beeprint import pyv 14 | from beeprint import pp 15 | from beeprint import Config 16 | from beeprint import constants as C 17 | 18 | try: 19 | from .definition import values 20 | import definition as df 21 | except: 22 | from definition import values 23 | import definition as df 24 | 25 | 26 | class TestBase(unittest.TestCase): 27 | 28 | def setUp(self): 29 | pass 30 | 31 | def load(self, rel_path): 32 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 33 | with codecs.open(data_path, encoding='utf8') as fp: 34 | ans = fp.read() 35 | return ans 36 | 37 | 38 | class TestSimpleTypes(TestBase): 39 | 40 | def test_positive(self): 41 | self.assertEqual(pp(1, output=False), '1\n') 42 | self.assertEqual(pp(1.1, output=False), '1.1\n') 43 | 44 | def test_negative(self): 45 | self.assertEqual(pp(-1, output=False), '-1\n') 46 | self.assertEqual(pp(-1.1, output=False), '-1.1\n') 47 | 48 | def test_string(self): 49 | # string literal 50 | config = Config() 51 | config.str_display_not_prefix_u = False 52 | config.str_display_not_prefix_b = False 53 | self.assertEqual(pp("plain string", output=False, config=config), "'plain string'\n") 54 | 55 | # unicode string 56 | s = u'unicode string' 57 | if pyv == 2: 58 | self.assertEqual(pp(s, output=False, config=config), u"u'unicode string'\n") 59 | else: 60 | self.assertEqual(pp(s, output=False, config=config), u"'unicode string'\n") 61 | 62 | # utf8 string 63 | s = u'utf8 string'.encode('utf-8') 64 | if pyv == 2: 65 | self.assertEqual(pp(s, output=False, config=config), u"'utf8 string'\n") 66 | else: 67 | self.assertEqual(pp(s, output=False, config=config), u"b'utf8 string'\n") 68 | 69 | # gb2312 string 70 | s = u'gb2312 string'.encode('gb2312') 71 | if pyv == 2: 72 | self.assertEqual(pp(s, output=False, config=config), u"'gb2312 string'\n") 73 | else: 74 | self.assertEqual(pp(s, output=False, config=config), u"b'gb2312 string'\n") 75 | 76 | # unicode special characters string 77 | s = u'\\' 78 | if pyv == 2: 79 | self.assertEqual(pp(s, output=False, config=config), u"u'\\\\'\n") 80 | else: 81 | self.assertEqual(pp(s, output=False, config=config), u"'\\\\'\n") 82 | 83 | # utf8 special characters string 84 | s = u'\\'.encode("utf8") 85 | if pyv == 2: 86 | self.assertEqual(pp(s, output=False, config=config), u"'\\\\'\n") 87 | else: 88 | self.assertEqual(pp(s, output=False, config=config), u"b'\\\\'\n") 89 | 90 | def test_complicate_data(self): 91 | config = Config() 92 | config.string_break_method = C._STRING_BREAK_BY_WIDTH 93 | config.text_autoclip_enable = False 94 | 95 | ans = u"" 96 | data_path = os.path.join(CUR_SCRIPT_PATH, 97 | 'data/all_in_one.txt') 98 | with codecs.open(data_path, encoding="utf8") as fp: 99 | ans = fp.read() 100 | 101 | res = pp(values, output=False, config=config) 102 | res += pp(df.long_text_in_dict, output=False, config=config) 103 | res += pp(df.long_text_in_list, output=False, config=config) 104 | self.assertEqual(res, ans) 105 | # self.assertEqual(res, ans) 106 | 107 | def test_out_of_range(self): 108 | config = Config() 109 | config.max_depth = 1 110 | 111 | ans = "" 112 | rel_path = '' 113 | if pyv == 2: 114 | rel_path = 'data/out_of_range_py2.txt' 115 | else: 116 | rel_path = 'data/out_of_range_py3.txt' 117 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 118 | with codecs.open(data_path, encoding='utf8') as fp: 119 | ans = fp.read() 120 | # delete object id, such as 121 | # 122 | ans, _ = re.subn("at 0x[\d\w]+", "", ans) 123 | 124 | res = pp(df.out_of_range, output=False, config=config) 125 | # delete object id, such as 126 | # 127 | res, _ = re.subn("at 0x[\d\w]+", "", res) 128 | 129 | self.assertEqual(res, ans) 130 | 131 | def test_out_of_range_in_dict(self): 132 | config = Config() 133 | config.max_depth = 1 134 | 135 | ans = "" 136 | rel_path = 'data/out_of_range_in_dict.txt' 137 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 138 | with codecs.open(data_path, encoding='utf8') as fp: 139 | ans = fp.read() 140 | # delete object id, such as 141 | # 142 | ans, _ = re.subn("at 0x[\d\w]+", "", ans) 143 | 144 | res = pp(df.out_of_range_in_dict, output=False, config=config) 145 | # delete object id, such as 146 | # 147 | res, _ = re.subn("at 0x[\d\w]+", "", res) 148 | 149 | self.assertEqual(res, ans) 150 | 151 | 152 | def test_long_text(self): 153 | pass 154 | # res = pp(long_text_en, output=False, config=config) 155 | # self.assertEqual(res, ans) 156 | 157 | def test_recursion(self): 158 | ans = self.load('data/recursion.txt') 159 | res = df.test_recursion(output=False) 160 | 161 | res, _ = re.subn("id=\d+>", "", res) 162 | ans, _ = re.subn("id=\d+>", "", ans) 163 | self.assertEqual(res, ans) 164 | 165 | 166 | class TestLineBreak(TestBase): 167 | 168 | def test_boundary_break(self): 169 | rel_path = 'data/string_break/boundary_break.txt' 170 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 171 | with codecs.open(data_path, encoding='utf8') as fp: 172 | ans = fp.read() 173 | 174 | res = df.test_boundary_break(False) 175 | 176 | self.assertEqual(ans, res) 177 | 178 | 179 | class TestAutoClip(TestBase): 180 | 181 | def test_3lines_clip(self): 182 | rel_path = 'data/auto_clip/by_lines.txt' 183 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 184 | with codecs.open(data_path, encoding='utf8') as fp: 185 | ans = fp.read() 186 | 187 | res = df.test_3lines_clip(False) 188 | 189 | self.assertEqual(ans, res) 190 | 191 | def test_no_room(self): 192 | rel_path = 'data/auto_clip/no_room.txt' 193 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 194 | with codecs.open(data_path, encoding='utf8') as fp: 195 | ans = fp.read() 196 | 197 | res = df.test_autoclip_no_room(False) 198 | 199 | self.assertEqual(ans, res) 200 | 201 | 202 | class TestDict(TestBase): 203 | 204 | def test_ordered_keys(self): 205 | rel_path = 'data/dict/foo.txt' 206 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 207 | with codecs.open(data_path, encoding='utf8') as fp: 208 | ans = fp.read() 209 | 210 | res = df.test_dict_ordered_keys(False) 211 | 212 | self.assertEqual(ans, res) 213 | 214 | def test_comma_ending(self): 215 | rel_path = 'data/dict/comma.txt' 216 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 217 | with codecs.open(data_path, encoding='utf8') as fp: 218 | ans = fp.read() 219 | 220 | res = pp({'key': 'aaa,,,'}, output=False) 221 | self.assertEqual(ans, res) 222 | 223 | 224 | class TestTuple(TestBase): 225 | 226 | def test_ordered_keys(self): 227 | rel_path = 'data/tuple/simple.txt' 228 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 229 | with codecs.open(data_path, encoding='utf8') as fp: 230 | ans = fp.read() 231 | 232 | res = df.test_tuple(False) 233 | 234 | self.assertEqual(ans, res) 235 | 236 | def test_nested(self): 237 | ans = self.load('data/tuple/nested.txt') 238 | 239 | res = df.test_tuple_nested(False) 240 | 241 | self.assertEqual(ans, res) 242 | 243 | 244 | class TestInlineRepr(TestBase): 245 | 246 | def test_inline_repr_out_of_range(self): 247 | rel_path = 'data/inline_repr/out_of_range.txt' 248 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 249 | with codecs.open(data_path, encoding='utf8') as fp: 250 | ans = fp.read() 251 | ans, _ = re.subn("(len=\d+)", "", ans) 252 | 253 | res = df.test_inline_repr_out_of_range(False) 254 | res, _ = re.subn("(len=\d+)", "", res) 255 | 256 | self.assertEqual(ans, res) 257 | 258 | 259 | class TestClass(TestBase): 260 | 261 | def test_class_last_el(self): 262 | rel_path = 'data/class/last_el.txt' 263 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 264 | with codecs.open(data_path, encoding='utf8') as fp: 265 | ans = fp.read() 266 | 267 | res = df.test_class_last_el(False) 268 | 269 | self.assertEqual(ans, res) 270 | 271 | def test_class_all_repr_disable(self): 272 | rel_path = 'data/class/all_repr_disable.txt' 273 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 274 | with codecs.open(data_path, encoding='utf8') as fp: 275 | ans = fp.read() 276 | 277 | res = df.test_class_all_repr_disable(False) 278 | 279 | self.assertEqual(ans, res) 280 | 281 | def test_class_inst_repr_enable(self): 282 | rel_path = 'data/class/inst_repr_enable.txt' 283 | data_path = os.path.join(CUR_SCRIPT_PATH, rel_path) 284 | with codecs.open(data_path, encoding='utf8') as fp: 285 | ans = fp.read() 286 | 287 | res = df.test_class_inst_repr_enable(False) 288 | 289 | self.assertEqual(ans, res) 290 | 291 | if __name__ == '__main__': 292 | unittest.main() 293 | -------------------------------------------------------------------------------- /tests/t.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | from __future__ import absolute_import 4 | 5 | from imp import reload 6 | 7 | import unittest 8 | import os 9 | import sys 10 | import types 11 | import inspect 12 | import codecs 13 | import re 14 | 15 | 16 | CUR_SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) 17 | BEEPRINT_PATH = os.path.abspath(os.path.join(CUR_SCRIPT_PATH, '..')) 18 | sys.path.insert(0, BEEPRINT_PATH) 19 | 20 | if sys.version_info < (3, 0): 21 | # avoid throw [UnicodeEncodeError: 'ascii' codec can't encode characters] 22 | # exceptions, without these lines, the sys.getdefaultencoding() returns ascii 23 | reload(sys) 24 | sys.setdefaultencoding('utf-8') 25 | 26 | pyv = 2 27 | else: 28 | unicode = str 29 | pyv = 3 30 | 31 | from pprintpp import pprint as ppp 32 | from beeprint import pp, pyv, Config 33 | from beeprint import constants as C 34 | 35 | 36 | try: 37 | from .definition import values 38 | from .definition import inst_of_normal_class_old_style, inst_of_normal_class_new_style, NormalClassOldStyle, NormalClassNewStyle, EmptyFunc 39 | from . import definition as df 40 | except: 41 | from definition import values 42 | from definition import inst_of_normal_class_old_style, inst_of_normal_class_new_style, NormalClassOldStyle, NormalClassNewStyle, EmptyFunc 43 | import definition as df 44 | 45 | 46 | # >> utilities 47 | def detect_same_attrs(*args): 48 | d_attrs_by_val_list = {} 49 | for e in args: 50 | for attr in dir(e): 51 | d_attrs_by_val_list.setdefault(attr, []) 52 | d_attrs_by_val_list[attr].append(e) 53 | 54 | same_attrs = [] 55 | for attr, values in d_attrs_by_val_list.items(): 56 | if len(values) == len(args): 57 | same_attrs.append((attr, values)) 58 | 59 | same_attrs.sort(key=lambda e: e[0]) 60 | 61 | return same_attrs 62 | # << utilities 63 | 64 | def class_test(): 65 | pass 66 | 67 | def inst_test(): 68 | for v in values: 69 | try: 70 | print('%40s: %s' % (v, v.__module__)) 71 | except: 72 | pass 73 | continue 74 | if pyv == 2: 75 | print('%40s: %s' % (v, isinstance(v, (types.InstanceType, object)))) 76 | else: 77 | print('%40s: %s' % (v, isinstance(v, object))) 78 | 79 | same_attrs = detect_same_attrs(inst_of_normal_class_old_style, inst_of_normal_class_new_style) 80 | for attr, v in same_attrs: 81 | print('%40s: %s' % (attr, v)) 82 | 83 | def builtin_test(): 84 | for v in [EmptyFunc, NormalClassOldStyle.mth, NormalClassNewStyle.mth, inst_of_normal_class_old_style.mth, inst_of_normal_class_new_style.mth]: 85 | # print('%40s: %s' % (v, isinstance(v, types.MethodType))) py2 all true 86 | # print('%40s: %s' % (v, inspect.ismethod(v))) py2 all true 87 | # print('%40s: %s' % (v, inspect.isbuiltin(v))) py2 all false 88 | # print('%40s: %s' % (v, inspect.ismethod(v))) py3 FFTT 89 | print('%40s: %s, %s' % (v, v.__qualname__, inspect.getargspec(v).args)) 90 | 91 | 92 | args = { 93 | "class_test": class_test, 94 | "inst_test": inst_test, 95 | "builtin_test": builtin_test, 96 | } 97 | 98 | def has_custom_repr(o): 99 | repr_typ_name = lambda o: type(o.__repr__).__name__ 100 | builtin_repr_names = ['method-wrapper', 'wrapper_descriptor', 'method-wrapper'] 101 | return hasattr(o, '__repr__') and repr_typ_name(o) not in builtin_repr_names 102 | 103 | 104 | def get_decorators(o): 105 | sourcelines = inspect.getsourcelines(o)[0] 106 | decorators = [] 107 | for line in sourcelines: 108 | line = line.strip() 109 | if line.startswith('@'): 110 | match = re.search('@(.*)[\(]*', line) 111 | if not match: 112 | print('unmatch line', line) 113 | continue 114 | decorators.append(match.group(1)) 115 | 116 | return decorators 117 | 118 | 119 | def test_class_repr(): 120 | reprs = [ 121 | df.ReprMethodClassOldStyle, 122 | df.ReprMethodClassNewStyle, 123 | df.ReprStaticClassOldStyle, 124 | df.ReprStaticClassNewStyle, 125 | df.ReprClassMethodClassOldStyle, 126 | df.ReprClassMethodClassNewStyle, 127 | df.ReprLambdaClassOldStyle, 128 | df.ReprLambdaClassNewStyle, 129 | ] 130 | values = [ 131 | df.EmptyFunc, 132 | df.EmptyClassOldStyle, 133 | df.EmptyClassNewStyle, 134 | df.NormalClassOldStyle, 135 | df.NormalClassNewStyle, 136 | df.inst_of_normal_class_old_style, 137 | df.inst_of_normal_class_new_style, 138 | df.ReprMethodClassNewStyle, 139 | ] 140 | typ = lambda e: type(e.__repr__).__name__ 141 | for c in values+reprs: 142 | print('%60s %s' % (c, has_custom_repr(c))) 143 | 144 | print() 145 | for c in reprs: 146 | # print('%60s %20s %20s' % (c, typ(c), typ(c()))) 147 | print('%60s %s:%20s %s:%20s' % (c, get_decorators(c.__repr__), typ(c), get_decorators(c().__repr__), typ(c()))) 148 | # print('%60s %20s %-20s' % (c, typ(c), repr(c()))) 149 | 150 | class A: 151 | a =1 152 | b =2 153 | c= '3333' 154 | 155 | def main(): 156 | a = A() 157 | pp(a) 158 | 159 | if __name__ == '__main__': 160 | main() 161 | -------------------------------------------------------------------------------- /tests/test_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import unittest 4 | import os 5 | import sys 6 | import codecs 7 | 8 | CUR_SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) 9 | BEEPRINT_PATH = os.path.abspath(os.path.join(CUR_SCRIPT_PATH, '..')) 10 | sys.path.append(BEEPRINT_PATH) 11 | 12 | from beeprint.printer import beeprint, pyv 13 | from beeprint import settings as S 14 | from beeprint import helper 15 | 16 | 17 | class TestHelper(unittest.TestCase): 18 | 19 | def setUp(self): 20 | pass 21 | 22 | def test_class_method(self): 23 | self.assertEqual(helper.inline_msg(1), '1') 24 | self.assertEqual(helper.inline_msg('aaaaaaaaaaaaaaaaaaaaaaaaaaaa'*10), 25 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...(len=280)') 26 | self.assertEqual(helper.inline_msg(os)[:18], 27 | "