The hardest problem in computer science...

Larry Hudson orgnut at
Fri Jan 6 22:12:52 EST 2017

On 01/06/2017 05:03 AM, Steve D'Aprano wrote:
> The second hardest problem in computer science is cache invalidation.
> The *hardest* problem is naming things.
> In a hierarchical tree view widget that displays items like this:
>     Fiction
>     ├─ Fantasy
>     │  ├─ Terry Pratchett
>     │  │  ├─ Discworld

> but what do I call XXX and YYY?
> Seriously-considering-just-hard-coding-them-as-magic-constants-ly y'rs,

I don't know if this is helpful or even relevant, but here is a class for using these 
box-drawing characters that I wrote for my own use.  (I'm just a hobby programmer...)
It's not very elegant and not particularly convenient to use, but it is usable.  And I hesitate 
to post it because it is so long (approx 90 line docstring to explain its use, and ending with 
some test code).  In any case, here it is...


BoxChr class to handle box-drawing characters.

The character is selected by a two-character string:
     'tl', 'tm', 'tr' -- Top Left, Top Mid, Top Right:  ┌ ┬ ┐
     'ml', 'mm', 'mr' -- Mid Left, Mid Mid, Mid Right:  ├ ┼ ┤
     'bl', 'bm', 'br' -- Bot Left, Bot Mid, Bot Right:  └ ┴ ┘
     'hz', 'vt'       -- Horizontal and Vertical lines: ─ │
     Case is ignored.  Invalid selection string returns a dot:  '·'
     NOTE:  It is currently disabled, but the code is still
         available to handle small and large dots with 'dt' and 'dd'.
         These both ignore the style.

The style is selected by a two-character string:
     The characters are 's', 'd' and 'b' for single, double and bold.
     The first character defines the horizontal components,
         the second defines the vertical components.
     The valid styles are 'ss', 'sd', 'sb', 'ds', 'dd', 'bs', 'bb'.
     The potential styles 'db' and 'bd' are invalid.
     The class defaults to 'ss' -- single horizontal, single vertical.
     Case is ignored.  Invalid style string raises ValueError.

NOTE:  The following examples assume bc has been set to a BoxChr class.
     bc = BoxChr()       #   Style defaults to 'ss'
     bc = BoxChr('sd')   #   Style set to single/double, or whatever desired.
     (Examples assume 'ss' style.)

     style:  set or return the style-code string.
             Case is ignored.  Invalid style raises ValueError.
         Examples: = 'ds' -- sets style to double/single.
             st = -- sets variable st to current style code.

     [] (__getitem__):  Returns selected box character.
             Case is ignored.  Invalid selector returns a dot:  '·'
         Example:  bc['tm'] returns '┬'

     bxstr():   Returns a string created from a sequence of box character codes
             and 'normal' characters.  (in this description 'selector' refers
             to the two-character codes as defined above.)

             Each element of the given sequence (list or tuple) must be a
             string, either a series of selector codes, or a 'normal' string
             (one without any box selectors).  The box character selector string
             must start with 'BX' followed by the selector codes, separated by
             spaces.  The 'BX' must be upper case, but the case of the selector
             codes is ignored.  A space between the 'BX' prefix and the first
             selector code is optional.  This selector string can only contain
             selector codes.  'Normal' characters are simply given as normal
             strings, interspersed with the selector strings in the given

             If the selection is only box characters, it can opionally be passed
             as a single string rather than enclosing it in a list.
             seq = ['BX ml hz', ' TEXT ', 'BX hz mr']
             bc.bxstr(seq) returns '├─ TEXT ─┤'
             If you need a string beginning with 'BX' it has to be given
             as a single-character string 'B' followed by a string containing
             the remaining text.  Example 'BX etc' must be given as:
             ['B', 'X etc'].

     boxtext():  Create boxed text, returned as a single string with
             embedded newlines.

             txt     The text to use
             tsize   Expand tabs to specified number of spaces,  Default is 4
             just    '<', '^' or '>' as left/center/right justification.
                     Default is '<' — left-justified.
             tall    If True, inserts a blank line above and below the text.
                     If False, no extra blank lines are used.
                     Default is True.

             Text can be either a single string with embedded newlines (ie. a
             triple-quoted string) or list of strings.  Trailing blank lines
             are removed.  Center and right justification will strip whitespace
             from both ends of the lines.  Left justification (the default)
             only strips trailing whitespace.

             Width is based on the length of the longest line, plus two spaces
             before and after.

class BoxChr:
     def __init__(self, style='ss'):
         self._styles = ('ss', 'sd', 'sb', 'ds', 'dd', 'bs', 'bb')
         self._keys = ('tl', 'tm', 'tr', 'ml', 'mm', 'mr',
                 'bl', 'bm', 'br', 'hz', 'vt')       #   'dt', 'dd')
         self._boxchrs = {
             'tl' : ('┌', '╓', '┎', '╒', '╔', '┍', '┏'),
             'tm' : ('┬', '╥', '┰', '╤', '╦', '┯', '┳'),
             'tr' : ('┐', '╖', '┒', '╕', '╗', '┑', '┓'),
             'ml' : ('├', '╟', '┠', '╞', '╠', '┝', '┣'),
             'mm' : ('┼', '╫', '╂', '╪', '╬', '┿', '╋'),
             'mr' : ('┤', '╢', '┨', '╡', '╣', '┥', '┫'),
             'bl' : ('└', '╙', '┖', '╘', '╚', '┕', '┗'),
             'bm' : ('┴', '╨', '┸', '╧', '╩', '┷', '┻'),
             'br' : ('┘', '╜', '┚', '╛', '╝', '┙', '┛'),
             'hz' : ('─', '─', '─', '═', '═', '━', '━'),
             'vt' : ('│', '║', '┃', '│', '║', '│', '┃')
#           'dt' : ('·', '·', '·', '·', '·', '·', '·'),
#           'dd' : ('•', '•', '•', '•', '•', '•', '•')

     def _setStyle(self, style):
         """Set the class style"""
             self._style = self._styles.index(style.lower())
         except IndexError:
             raise ValueError

     def _getStyle(self):
         """Return the class style code"""
         return self._styles[self._style]

     style = property(_getStyle, _setStyle)

     def __getitem__(self, key):
         """Select and return the box character"""
             return self._boxchrs[key.lower()][self._style]
         except KeyError:
             return '·'

     def bxstr(self, slst):
         """Create a string from a given sequence of strings"""
         def cnvst(s):
             """Convert selector string to box character string"""
             bs = []
             for sc in s:                #   For each selector code
                 bs.append(self.__getitem__(sc)) #   Get box char
             return ''.join(bs)

         out = []
         if isinstance(slst, str):       #   Input is string?
             slst = [slst]               #   Make it a list
         for ss in slst:                 #   For each string in list
             if ss.startswith('BX'):     #   Check if selector or normal string
                 out.append(cnvst(ss[2:].split()))   #   Selector
                 out.append(ss)          #   Normal string
         return ''.join(out)

     def boxtext(self, txt, tsize=4, just='<', tall=True):
         """Create boxed text"""
         if just not in '<^>':           #   Check for valid justification code
             just = '<'
         if isinstance(txt, str):        #   Check for string input
             txt = txt.split('\n')       #   Convert to list
         while txt[-1].rstrip() == '':   #   Delete trailing blank lines
             txt = txt[:-1]
         if just == '<':                 #   Left just, only strip on right
             txt = [line.rstrip() for line in txt]
         else:                           #   Otherwise strip both ends
             txt = [line.strip() for line in txt]
         txt = [line.expandtabs(tsize) for line in txt]  #   Cnvt tabs to spaces
         maxlen = max([len(line) for line in txt]) + 4   #   Find longest line

         #   Create the boxed text
         out = []
         out.append(self.bxstr('BXtl ' + 'hz ' * maxlen + 'tr'))
         if tall:
             out.append(self.bxstr(['BXvt', ' ' * maxlen, 'BXvt']))
         for line in txt:
             out.append(self.bxstr(['BXvt', '  {:{}{}}  '.
                     format(line, just, maxlen-4), 'BXvt']))
         if tall:
             out.append(self.bxstr(['BXvt', ' ' * maxlen, 'BXvt']))
         out.append(self.bxstr('BXbl ' + 'hz ' * maxlen + 'br'))
         return '\n'.join(out)

if __name__ == '__main__':
     def grid(bc):
         """Print a box"""
         print(bc.bxstr(['BXtl hz hz hz TM hz Hz hZ tr']))
         print(bc.bxstr(['BXvt', '   ', 'BXvt', '   ', 'BXvt']))
         print(bc.bxstr('BX ml hz hz hz mm hz hz hz mr'))
         print(bc.bxstr(['BX vt', '   ', 'BXvt', '   ', 'BXvt']))
         print(bc.bxstr('BXbl hz hz hz bm hz hz hz br'))

     bc = BoxChr()
     #   Check all chars in all styles
     for style in bc._styles: = style

     #   Verify error test
     try: = 'xx'
     except ValueError:
         print('Invaled style')

     #   Test boxtext() method
     jab = """'Twas brillig, and the slithy toves
\tDid gyre and gimbol in the wabe.
All mimsy were the borogoves,
\tAnd the momraths outgrabe.
     print(bc.boxtext(jab, tsize=8))
     print(bc.boxtext(jab, just='^'))
     print(bc.boxtext(jab, just='>'))
     print(bc.boxtext(jab, tall=False))


      -=- Larry -=-

More information about the Python-list mailing list