def render_view(self, render_prologue=True): """Returns a GroupView""" log = LoggerAdapter(logger, {'name_ext': 'GroupModel.render_view'}) log.debug("Entering") #- get number of digits in the number of object models max_line_num_strlen = len(str(len(self.object_models))) log.debug("max_line_num_strlen: {}".format(max_line_num_strlen)) next_color = None #- keep track of all the lengths of the attributes #- across objects (so we can line them all up when we render a view) attr_maxlens = dict() for n, object_model_x in enumerate(self.object_models): #- override object model colors with group colors, if set next_color = self.get_next_color() if next_color: log.debug( "setting object model color to next_color: {}".format( next_color)) object_model_x.set_colors([next_color], match_delimiter=True) if render_prologue: #- add the group sequence index as leading output of the object view index_str = '{current_index: <{max_line_num_strlen}}: '.format(\ current_index=str(n), max_line_num_strlen=max_line_num_strlen) log.debug("index_str: '{}'".format(index_str)) #- match proglogue color to object color prologue = ColoredText(index_str, next_color) object_model_x.prologue = prologue if self.align: #- code to line up the attribute lengths across objects for attribute_model_x in object_model_x.attribute_models: attr_name = attribute_model_x.name attr_width = attribute_model_x.get_width() log.debug( f"attr_name:'{attr_name}' | attr_width: {attr_width}") try: attr_maxlens[attr_name] = max(attr_maxlens[attr_name], attr_width) except KeyError: attr_maxlens[attr_name] = attr_width object_views = list() for object_model_x in self.object_models: if self.align: #- dynamically align attribute lengths #- TODO: if each attribute has a defined length, skip this for attribute_model_x in object_model_x.attribute_models: attribute_model_x.length = attr_maxlens[ attribute_model_x.name] object_views.append(object_model_x.render_view()) groupView = GroupView(object_views=object_views) log.debug("Returning groupView: {}".format(groupView)) return groupView
def __init__(self, text, prologue=None, epilogue=None, color=None): """ text: the text that is the rendered attribute prologue: stuff you want to prepend to the rendered attribute epilogue: stuff you want to append to the rendered attribute """ self.text = text self.render_method = print self.color = color if prologue is None: prologue = '' self.prologue = ColoredText(prologue) if epilogue is None: epilogue = '' self.epilogue = ColoredText(epilogue)
def get_render_output(self): outputs = list() if self.color: outputs.append(self.prologue.render()) outputs.append(ColoredText(self.text, self.color).render()) outputs.append(self.epilogue.render()) else: outputs.append(self.prologue.plain()) outputs.append(str(self.text)) outputs.append(self.epilogue.plain()) return ''.join(outputs)
def __init__(self, attribute_views, delimiters, prologue=None): """ Input: attribute_views: list of AttributeView objects delimiter: ColoredText instance string used to separate AttributeViews when rendering """ self.attribute_views = attribute_views self.delimiters = [None] * len(delimiters) if delimiters: for i, delim in enumerate(delimiters[:]): if isinstance(delim, str): delimiters[i] = ColoredText(delim, color=None) self.delimiters = cycle(delimiters) if delimiters else repeat( ColoredText('', color=None)) self.prologue = prologue if prologue else ColoredText('') self.render_method = print self.term_size = shutil.get_terminal_size()
def get_render_data(self): """ Description: return self as a collection of ColoredText instances """ log = LoggerAdapter(logger, {'name_ext' : '{}.get_render_data'.format(\ self.__class__.__name__)}) outputs = list() outputs.append(self.prologue) outputs.append(ColoredText(self.text, self.color)) outputs.append(self.epilogue) ard = AttributeRenderDatum(*outputs) log.debug("returning: {}".format(ard)) return ard
def render_object_from_spec(self, obj, spec, colors='_follow_object_spec_', align=True): """ Description: render an object by using an ObjectModelSpec to get the rendering specs Input: obj: the object to be rendered spec: the ObjectModelSpec to use Notes: Creates a GroupModel and renders that All the other render_object* methods end up calling this one """ log = LoggerAdapter(logger, {'name_ext':'{}.render_object_from_spec'.format(\ self.__class__.__name__)}) log.debug("Entering: obj: {} | spec: {} | colors: {} | align: {}".format(\ obj, spec, colors, align)) #- go through and first create the ObjectModels obj_models = list() if self.is_render_iterable(obj): for i, obj_x in enumerate(obj): obj_models.append( ObjectModel(obj_x, spec, colors=next(spec.colors))) else: prologue = ColoredText('') obj_models = [ObjectModel(obj, spec, colors=next(spec.colors))] if colors == '_follow_object_spec_': colors = copy.copy(spec.colors) #- collect the ObjectModels in a GroupModel group_model = GroupModel(object_models=obj_models, colors=colors, align=align) #-TODO: wrap GroupModel in CollectionModel here return group_model.render()
def render_view(self): """Return an ObjectView""" log = LoggerAdapter(logger, {'name_ext' : 'ObjectModel.render_view'}) attribute_views = list() delimiters = list() for n,attribute_model_x in enumerate(self.attribute_models): if self.colors: #- override AttributeModel color with ObjectModel color color = self.get_next_color() attribute_model_x.set_color(color) log.debug("set AttributeModel color: {}: {}".format(color,\ attribute_model_x)) attribute_views.append(attribute_model_x.render_view()) num_delims = 0 delimiters = list() for attr in range(len(self.attribute_models) - 1): delimiters.append(ColoredText(self.delimiter, next(self.delimiter_colors))) log.debug("Created {} delimiters".format(len(delimiters))) return ObjectView(attribute_views=attribute_views,\ delimiters=delimiters, prologue=self.prologue)
class AttributeView(ViewABC): """ Description: Attribute Views are the lowest-level views. They are nothing more than text that represents the value of an individual object's singular attribute wrapped in a ColoredText object. """ def __init__(self, text, prologue=None, epilogue=None, color=None): """ text: the text that is the rendered attribute prologue: stuff you want to prepend to the rendered attribute epilogue: stuff you want to append to the rendered attribute """ self.text = text self.render_method = print self.color = color if prologue is None: prologue = '' self.prologue = ColoredText(prologue) if epilogue is None: epilogue = '' self.epilogue = ColoredText(epilogue) def get_render_output(self): outputs = list() if self.color: outputs.append(self.prologue.render()) outputs.append(ColoredText(self.text, self.color).render()) outputs.append(self.epilogue.render()) else: outputs.append(self.prologue.plain()) outputs.append(str(self.text)) outputs.append(self.epilogue.plain()) return ''.join(outputs) def get_render_data(self): """ Description: return self as a collection of ColoredText instances """ log = LoggerAdapter(logger, {'name_ext' : '{}.get_render_data'.format(\ self.__class__.__name__)}) outputs = list() outputs.append(self.prologue) outputs.append(ColoredText(self.text, self.color)) outputs.append(self.epilogue) ard = AttributeRenderDatum(*outputs) log.debug("returning: {}".format(ard)) return ard def get_width(self): """ Description: text can be a newline-seperated stanza or a single string. the width of this attribute view is the length of the longest line in the text """ lines = self.text.split('\n') if len(lines) > 1: return len(max(*lines, key=len)) else: return len(lines[0]) def render(self): self.render_method(self.get_render_output()) def __repr__(self): outputs = list() outputs.append(self.__class__.__name__ + '(') outputs.append('text={}, '.format(self.text)) outputs.append('color={}, '.format(self.color)) outputs.append('prologue={}, '.format(self.prologue)) outputs.append('epilogue={}, '.format(self.epilogue)) return ''.join(outputs)
def test_init(self): ct = ColoredText(text='Hello World!', color='bright white on green') self.assertEqual(ct.to_str(), '\x1b[37m\x1b[42m\x1b[1mHello World!\x1b[0m')
def get_render_output(self, limit_to_screen=True): """ Description: Get the output that will be passed to the render method. (the text that will be printed to a terminal) Input: limit_to_screen: boolean controlling whether or not we cut off the line at the line-length of the terminal """ log = LoggerAdapter(logger, {'name_ext': 'ObjectView.get_render_output'}) #- figure out the width of each attribute view per_attr_widths = list() per_attr_colors = list() for n, attr_view in enumerate(self.attribute_views): attr_text = attr_view.get_render_data().attribute.plain() view_lines = attr_text.split('\n') if len(view_lines) <= 1: log.debug("single line attribute view") if n == 0: log.debug( f"adding prologue length to attr_width: {attr_text}") attr_width = len(attr_text) + len(self.prologue.plain()) else: log.debug( f"not adding prologue length to attr_width: {attr_text}" ) attr_width = len(attr_text) else: log.debug("multi-line attribute view: ") log.debug("{}".format(view_lines)) longest_line = max(*view_lines, key=len) attr_width = len(longest_line) + len(self.prologue.plain()) log.debug("attr_width : {}".format(attr_width)) per_attr_widths.append(attr_width) per_attr_colors.append(attr_view.color) log.debug("per_attr_widths: {}".format(per_attr_widths)) #- build a list of lines to render for each attribute view #- one is just the text, so the length of the line is the #- length of the visible characters #- then we have the lines per attribute that also contain color code #- the text lines we use to determine the display length #- but the manipulation of the attribute view must #- be done on the lines to actually be rendered per_attr_text_lines = [[]] * len(self.attribute_views) per_attr_lines = [[]] * len(self.attribute_views) for n, attr_view in enumerate(self.attribute_views): attr_rd = attr_view.get_render_data() text_lines = attr_rd.attribute.plain().split('\n') #- first line has prologue if n == 0 and self.prologue: text_lines[0] = self.prologue.plain() + text_lines[0] per_attr_text_lines[n] = text_lines per_attr_lines[n] = [ ColoredText(txt, attr_view.color) for txt in text_lines ] #- figure out how many lines this object will take up if per_attr_text_lines: object_view_lines = len(max(*per_attr_text_lines, key=len)) else: #- TODO: this assumes that we are always rendering attributes #- for builtins, that won't work object_view_lines = 0 log.debug(f"no attributes to render") log.debug("{} lines in this object view".format(object_view_lines)) #- combine the lists of attribute lines into a single list of object lines render_text_lines = list() render_lines = list() for line_num in range(object_view_lines): text_line = '' render_line = list() for m, attr_text_lines in enumerate(per_attr_text_lines): if len(attr_text_lines) > line_num: log.debug(f"m: {m} | line_num: {line_num}") log.debug(f"per_attr_widths[m]: {per_attr_widths[m]}") log.debug(("len(attr_text_lines[line_num]):" f"{len(attr_text_lines[line_num])}")) pad_len = per_attr_widths[m] - len( attr_text_lines[line_num]) log.debug(f"initial pad_len: {pad_len}") text_line += attr_text_lines[line_num] text_line += ' ' * pad_len cur_render_line = per_attr_lines[m][line_num] log.debug("cur_text_line initial:") log.debug(f"'{attr_text_lines[line_num]}'") log.debug("cur_render_line initial: ") log.debug(f"'{cur_render_line}'") cur_render_line.text += ' ' * pad_len log.debug("cur_render_line plus padding: ") log.debug(f"'{cur_render_line}'") render_line.append(cur_render_line) if m < len(per_attr_text_lines) - 1: delim = next(self.delimiters) text_line += delim.plain() render_line.append(delim) log.debug("render_line plus pad and delim:") log.debug("{}".format(render_line)) else: log.debug("adding padded stanza line") text_line += ' ' * per_attr_widths[m] log.debug("padded text line: ") log.debug(f"'{text_line}'") #- all attribute lines should have the same color for now color = per_attr_lines[m][0].color render_line.append( ColoredText(' ' * per_attr_widths[m], color)) log.debug("padded render line: ") log.debug("'{render_line}'") if m < len(per_attr_text_lines) - 1: delim = next(self.delimiters) text_line += delim.plain() render_line.append(delim) log.debug("padded render_line plus delim:") log.debug(f"{render_line}") render_text_lines.append(text_line) log.debug('rendered render_line:') log.debug(''.join([x.render() for x in render_line])) render_lines.append(render_line) if limit_to_screen: #- trim plaintext to fit screen width log.debug("screen size: {}".format(self.term_size.columns)) for n, line in enumerate(render_text_lines[:]): if len(line) > self.term_size.columns: length = len(line) log.debug( "trimming line #{} to fit screen (len={})".format( n, length)) render_text_lines[n] = line[0:self.term_size.columns] #- trim colored text to fit screen width for n, render_line in enumerate(render_lines[:]): running_text_width = 0 for m, item in enumerate(render_line[:]): current_text_width = running_text_width log.debug( "current text width: {}".format(current_text_width)) running_text_width += len(item.plain()) if running_text_width > self.term_size.columns: log.debug("detected overrun at {}-->{}".format(\ running_text_width, current_text_width)) log.debug("overrun item: {}".format(item)) extra = running_text_width - self.term_size.columns item.text = item.text[0:-extra] log.debug("trimmed item: {}".format(item)) render_line[m] = item render_lines[n] = render_line render_output = '' for n, render_line in enumerate(render_lines): for item in render_line: render_output += item.render() if n < len(render_lines) - 1: render_output += '\n' return render_output