def append(self, ucs): """ Update content buffer with additional lines of ansi unicodes. """ self.content.extend(Ansi(Ansi(ucs).decode_pipe()) .wrap(self.visible_width - 1).splitlines()) return self.move_end() or self.refresh(self.bottom)
def align(self, text, width=None): """ justify Ansi text alignment property and width. When None (default), the visible width after padding is used. """ return (Ansi(text).rjust if self.alignment == 'right' else Ansi(text).ljust if self.alignment == 'left' else Ansi(text). center)(width if width is not None else self.visible_width)
def fit_row(ucs): """ Strip a unicode row to fit window boundry, if necessary """ column = self.visible_width + 1 wrapped = Ansi(ucs).wrap(column).splitlines() if len(wrapped) > 1: marker = self.glyphs.get('strip', u' $') marker_column = self.visible_width - len(marker) wrapped = Ansi(ucs).wrap(marker_column).splitlines() ucs = Ansi(wrapped[0].rstrip()).ljust(marker_column) + marker return ucs return (Ansi(ucs).ljust(column))
def refresh_row(self, row): """ Return unicode byte sequence suitable for moving to location ypos of window-relative row, and displaying any valid entry there, or using glyphs['erase'] if out of bounds. Strings are ansi color safe, and will be trimmed using glyphs['strip'] if their displayed width is wider than window. """ import x84.bbs.session from x84.bbs.output import Ansi pos = self.pos(self.ypadding + row, self.xpadding) entry = self.vitem_shift + row if entry >= len(self.content): # out-of-bounds; return u''.join((pos, self.glyphs.get('erase', u' ') * self.visible_width,)) def fit_row(ucs): """ Strip a unicode row to fit window boundry, if necessary """ column = self.visible_width + 1 wrapped = Ansi(ucs).wrap(column).splitlines() if len(wrapped) > 1: marker = self.glyphs.get('strip', u' $') marker_column = self.visible_width - len(marker) wrapped = Ansi(ucs).wrap(marker_column).splitlines() ucs = Ansi(wrapped[0].rstrip()).ljust(marker_column) + marker return ucs return (Ansi(ucs).ljust(column)) term = x84.bbs.session.getterminal() # allow ucs data with '\r\n', to accomidate soft and hardbreaks; just # don't display them, really wrecks up cusor positioning. ucs = self.content[entry][1].strip(u'\r\n') # highlighted entry; strip of ansi sequences, use color 'highlight' # trim and append '$ ' if it cannot fit, if entry == self.index: ucs = Ansi(ucs).seqfill() if len(Ansi(ucs)) > self.visible_width: ucs = fit_row(ucs) return u''.join((pos, self.colors.get('highlight', u''), self.align(ucs), term.normal,)) # unselected entry; retain ansi sequences, decode any pipe characters, # trim and append '$ ' if it cannot fit ucs = Ansi(ucs).decode_pipe() if len(Ansi(ucs)) > self.visible_width: ucs = fit_row(ucs) return u''.join((pos, self.colors.get('lowlight', u''), self.align(ucs), term.normal,))
def update(self, ucs): """ Update content buffer with '\n'-delimited lines of Ansi. """ try: self.content = Ansi(Ansi(ucs).decode_pipe()).wrap( self.visible_width).splitlines() except AssertionError, err: # indeterminate length logger = logging.getLogger() logger.warn('%s in [%r]', err, ucs) self.content = ucs.split('\r\n')
def footer(self, ansi_text): """ Returns sequence that positions and displays unicode sequence 'ansi_text' at the bottom edge of the window. """ xloc = self.width / 2 - min(len(Ansi(ansi_text)) / 2, self.width / 2) return self.pos(max(0, self.height - 1), max(0, xloc)) + ansi_text
def title(self, ansi_text): """ Returns sequence that positions and displays unicode sequence 'ansi_text' at the title location of the window. """ xloc = self.width / 2 - min(len(Ansi(ansi_text)) / 2, self.width / 2) return self.pos(0, max(0, xloc)) + ansi_text
def backspace(self): """ Remove character from end of content buffer, scroll as necessary. """ if 0 == len(self.content): return u'' rstr = u'' # measured backspace erases over double-wide len_toss = len(Ansi(self.content[-1])) len_move = len(self.content[-1]) self.content = self.content[:-1] if (self.is_scrolled and (self._horiz_pos < self.scroll_amt)): # shift left, self._horiz_shift -= self.scroll_amt self._horiz_pos += self.scroll_amt rstr += self.refresh() else: rstr += u''.join(( self.fixate(0), u'\b' * len_toss, u' ' * len_move, u'\b' * len_move,)) self._horiz_pos -= 1 return rstr
def dummy_pager(): """ Provide interface without pager/lightbar. """ # pylint: disable=R0912 # Too many branches from x84.bbs import getterminal, echo, getch, Ansi term = getterminal() msg_header = u'// bbS liSt' hindent = 2 vindent = 5 nlines = 0 bbslist = get_bbslist() echo(u'\r\n' + msg_header.center(term.width).rstrip() + '\r\n\r\n') if 0 == len(bbslist): echo(u'\r\n\r\nNO BBSS. a%sdd ONE, q%sUit' % ( term.bold_blue(':'), term.bold_blue(':'))) while True: inp = getch() if inp in (u'q', 'Q'): return # quit elif inp in (u'a', 'A'): process_keystroke(inp) break while True: for (key, line) in bbslist: if key is None: # bbs software echo(term.blue_reverse(line.rstrip()) + '\r\n') nlines += 1 else: wrapd = Ansi(line).wrap(term.width - hindent) echo(term.bold_blue(key) + term.bold_black('. ')) for num, line in enumerate(wrapd.split('\r\n')): if num != 0: echo(' ' * hindent) echo(line + '\r\n') nlines += 1 if nlines and (nlines % (term.height - vindent) == 0): if more(True): return # one final prompt before exit if more(False): return return
def dummy_pager(): """ Provide interface without pager/lightbar. """ # pylint: disable=R0912 # Too many branches from x84.bbs import getterminal, echo, getch, Ansi term = getterminal() msg_header = u'// bbS liSt' hindent = 2 vindent = 5 nlines = 0 bbslist = get_bbslist() echo(u'\r\n' + msg_header.center(term.width).rstrip() + '\r\n\r\n') if 0 == len(bbslist): echo(u'\r\n\r\nNO BBSS. a%sdd ONE, q%sUit' % (term.bold_blue(':'), term.bold_blue(':'))) while True: inp = getch() if inp in (u'q', 'Q'): return # quit elif inp in (u'a', 'A'): process_keystroke(inp) break while True: for (key, line) in bbslist: if key is None: # bbs software echo(term.blue_reverse(line.rstrip()) + '\r\n') nlines += 1 else: wrapd = Ansi(line).wrap(term.width - hindent) echo(term.bold_blue(key) + term.bold_black('. ')) for num, line in enumerate(wrapd.split('\r\n')): if num != 0: echo(' ' * hindent) echo(line + '\r\n') nlines += 1 if nlines and (nlines % (term.height - vindent) == 0): if more(True): return # one final prompt before exit if more(False): return return
def more(cont=False): """ Returns True if user 'q'uit; otherwise False when prompting is complete (moar/next/whatever) """ from x84.bbs import echo, getch, Ansi, getterminal, LineEditor, DBProxy prompt_key = u'\r\n\r\nENtER BBS iD: ' msg_badkey = u'\r\n\r\nbbS id iNVAliD!' term = getterminal() prompt = u', '.join( fancy_blue(char, blurb) for char, blurb in (( 'i', 'NfO', ), ( 'a', 'dd', ), ( 'c', 'OMMENt', ), ( 'r', 'AtE', ), ( 't', 'ElNEt', ), ('v', 'ANSi'), ( 'q', 'Uit', ))) if cont: prompt += u', ' + fancy_blue(' ', 'more') prompt += u': ' while True: echo('\r\n' + Ansi(prompt).wrap(term.width - (term.width / 3))) inp = getch() if inp in (u'q', 'Q'): return True elif inp is not None and type(inp) is not int: if cont and inp == u' ': echo('\r\n\r\n') return False if inp.lower() in u'acrtviACRTVI': # these keystrokes require a bbs key argument, # prompt the user for one echo(prompt_key) key = LineEditor(5).read() if (key is None or 0 == len(key.strip()) or not key in DBProxy('bbslist')): echo(msg_badkey) continue process_keystroke(inp, key)
def process_keystroke(self, keystroke): """ Process the keystroke received by read method and take action. """ self._quit = False if keystroke in self.keyset['refresh']: return u'\b' * len(Ansi(self.content)) + self.refresh() elif keystroke in self.keyset['backspace']: if len(self.content) != 0: len_toss = len(Ansi(self.content[-1])) self.content = self.content[:-1] return u''.join(( u'\b' * len_toss, u' ' * len_toss, u'\b',)) elif keystroke in self.keyset['backword']: if len(self.content) != 0: ridx = self.content.rstrip().rfind(' ') + 1 toss = len(Ansi(self.content[ridx:])) move = len(self.content[ridx:]) self.content = self.content[:ridx] return u''.join(( u'\b' * toss, u' ' * move, u'\b' * move,)) elif keystroke in self.keyset['enter']: self._carriage_returned = True elif keystroke in self.keyset['exit']: self._quit = True elif type(keystroke) is int: return u'' elif (ord(keystroke) >= ord(' ') and (len(Ansi(self.content)) < self.width or self.width == 0)): self.content += keystroke return keystroke if not self.hidden else self.hidden return u''
def refresh_row(self, row): """ Return unicode string suitable for refreshing pager window at visible row. """ import x84.bbs.session term = x84.bbs.session.getterminal() ucs = (Ansi(self.visible_content[row]) if row < len(self.visible_content) else u'') return u''.join(( term.normal, self.pos(row + self.ypadding, self.xpadding), self.align(ucs), term.normal))
def refresh(self): """ Returns unicode byts suitable for drawing line. No movement or positional sequences are returned. """ from x84.bbs.session import getterminal term = getterminal() lightbar = u''.join(( term.normal, self.colors.get('highlight', u''), ' ' * self.width, '\b' * self.width)) content = (self.hidden * len(Ansi(self.content)) if self.hidden else self.content) return u''.join(( lightbar, content, term.cursor_visible))
def refresh(self): """ Return unicode sequence suitable for refreshing the entire line and placing the cursor. A strange by-product; if scrolling was not previously enabled, it is if wrapping must occur; this can happen if a non-scrolling editor was provided a very large .content buffer, then later .refresh()'d. -- essentially enabling infinate scrolling """ # reset position and detect new position from x84.bbs import getterminal term = getterminal() self._horiz_lastshift = self._horiz_shift self._horiz_shift = 0 self._horiz_pos = 0 for _count in range(len(Ansi(self.content))): if (self._horiz_pos > (self.visible_width - self.scroll_amt)): self._horiz_shift += self.scroll_amt self._horiz_pos -= self.scroll_amt self.enable_scrolling = True self._horiz_pos += 1 if self._horiz_shift > 0: self._horiz_shift += len(self.glyphs['strip']) prnt = u''.join(( self.glyphs['strip'], self.content[self._horiz_shift:],)) else: prnt = self.content return u''.join(( self.pos(self.ypadding, self.xpadding), term.normal, self.colors.get('highlight', u''), self.align(prnt), self.fixate(),))
def get_swinfo(entry, pager): """ given a normalized bbs software name, fetch a description paragraph for use in pager """ from x84.bbs import getterminal from x84.bbs.output import Ansi term = getterminal() output = pager.clear() if entry: entry = Ansi(entry).seqfill().strip() if entry and entry.strip().lower() == 'enthral': output += pager.update( "Enthral is a fresh look at the old school art of bbsing. " "It's a fresh face to an old favorite. Although Enthral is " "still in it's alpha stages, the system is quite stable and " "is already very feature rich. Currently available for " "Linux, BSD, and Apple's OS X.\r\n\r\n" " " + term.bold_blue('http://enthralbbs.com/') + "\r\n\r\n" "Author: Mercyful Fate\n" "IRC: #enthral on irc.bbs-scene.org\r\n") output += pager.title(u'- about ' + term.blue('Enthral') + u' -') elif entry and entry.strip().lower() == 'citadel': output += pager.update( "Ancient history.\r\n\r\n") output += pager.title(u'- about ' + term.blue('Citadel') + u' -') elif entry and entry.strip().lower() == 'mystic': output += pager.update( "Mystic BBS is a bulletin board system (BBS) software in " "the vein of other \"forum hack\" style software such as " "Renegade, Oblivion/2, and Iniquity. Like many of its " "counterparts it features a high degree of relatively " "easy customization thanks to its ACS based menu system " "along with fully editable strings and ANSI themes. " "Mystic also includes its own Pascal like MPL scripting " "language for even further flexibility.\r\n\r\n" " " + term.bold_blue('http://mysticbbs.com/') + "\r\n\r\n" "Author: g00r00\r\n" "IRC: #MysticBBS on irc.efnet.org\r\n") output += pager.title(u'- about ' + term.blue('Mystic') + u' -') elif entry and entry.strip().lower() == 'synchronet': output += pager.update( "Synchronet Bulletin Board System Software is a free " "software package that can turn your personal computer " "into your own custom online service supporting multiple " "simultaneous users with hierarchical message and file " "areas, multi-user chat, and the ever-popular BBS door " "games.\r\n\r\n" "Synchronet has since been substantially redesigned as " "an Internet-only BBS package for Win32 and Unix-x86 " "platforms and is an Open Source project under " "continuous development.\r\n\r\n" " " + term.bold_blue('http://www.synchro.net/\r\n') + "\r\n\r\n" "Author: Deuce\r\n" "IRC: #synchronet on irc.bbs-scene.org") output += pager.title(u'- about ' + term.blue('Synchronet') + u' -') elif entry and entry.strip().lower() == 'progressive': output += pager.update( "This bbs features threading, intra-process communication, " "and easy scripting in python. X/84 is a continuation of " "this codebase.\r\n\r\n" + "Author: jojo\r\n" "IRC: #prsv on irc.efnet.org") output += pager.title(u'- about ' + term.blue('The Progressive -')) elif entry and entry.strip().lower() == 'x/84': output += pager.update( "X/84 is an open source python utf8 bsd-licensed telnet " "server specificly designed for BBS's, MUD's, and high " "scriptability. It is a Continuation of 'The Progressive' " "and is the only BBS software to support both CP437 and " "UTF8 encoding.\r\n\r\n" " " + term.bold_blue('https://github.com/jquast/x84/\r\n') + "\r\n\r\nAuthor: dingo, jojo\r\n" "IRC: #prsv on irc.efnet.org") output += pager.title(u'- about ' + term.blue('X/84') + u' -') else: output += pager.update(u' no information about %s.' % (entry or u'').title(),) output += pager.title(u'- about ' + term.blue(entry or u'') + u' -') return output
def get_swinfo(entry, pager): """ given a normalized bbs software name, fetch a description paragraph for use in pager """ from x84.bbs import getterminal from x84.bbs.output import Ansi term = getterminal() output = pager.clear() if entry: entry = Ansi(entry).seqfill().strip() if entry and entry.strip().lower() == 'enthral': output += pager.update( "Enthral is a fresh look at the old school art of bbsing. " "It's a fresh face to an old favorite. Although Enthral is " "still in it's alpha stages, the system is quite stable and " "is already very feature rich. Currently available for " "Linux, BSD, and Apple's OS X.\r\n\r\n" " " + term.bold_blue('http://enthralbbs.com/') + "\r\n\r\n" "Author: Mercyful Fate\n" "IRC: #enthral on irc.bbs-scene.org\r\n") output += pager.title(u'- about ' + term.blue('Enthral') + u' -') elif entry and entry.strip().lower() == 'citadel': output += pager.update("Ancient history.\r\n\r\n") output += pager.title(u'- about ' + term.blue('Citadel') + u' -') elif entry and entry.strip().lower() == 'mystic': output += pager.update( "Mystic BBS is a bulletin board system (BBS) software in " "the vein of other \"forum hack\" style software such as " "Renegade, Oblivion/2, and Iniquity. Like many of its " "counterparts it features a high degree of relatively " "easy customization thanks to its ACS based menu system " "along with fully editable strings and ANSI themes. " "Mystic also includes its own Pascal like MPL scripting " "language for even further flexibility.\r\n\r\n" " " + term.bold_blue('http://mysticbbs.com/') + "\r\n\r\n" "Author: g00r00\r\n" "IRC: #MysticBBS on irc.efnet.org\r\n") output += pager.title(u'- about ' + term.blue('Mystic') + u' -') elif entry and entry.strip().lower() == 'synchronet': output += pager.update( "Synchronet Bulletin Board System Software is a free " "software package that can turn your personal computer " "into your own custom online service supporting multiple " "simultaneous users with hierarchical message and file " "areas, multi-user chat, and the ever-popular BBS door " "games.\r\n\r\n" "Synchronet has since been substantially redesigned as " "an Internet-only BBS package for Win32 and Unix-x86 " "platforms and is an Open Source project under " "continuous development.\r\n\r\n" " " + term.bold_blue('http://www.synchro.net/\r\n') + "\r\n\r\n" "Author: Deuce\r\n" "IRC: #synchronet on irc.bbs-scene.org") output += pager.title(u'- about ' + term.blue('Synchronet') + u' -') elif entry and entry.strip().lower() == 'progressive': output += pager.update( "This bbs features threading, intra-process communication, " "and easy scripting in python. X/84 is a continuation of " "this codebase.\r\n\r\n" + "Author: jojo\r\n" "IRC: #prsv on irc.efnet.org") output += pager.title(u'- about ' + term.blue('The Progressive -')) elif entry and entry.strip().lower() == 'x/84': output += pager.update( "X/84 is an open source python utf8 bsd-licensed telnet " "server specificly designed for BBS's, MUD's, and high " "scriptability. It is a Continuation of 'The Progressive' " "and is the only BBS software to support both CP437 and " "UTF8 encoding.\r\n\r\n" " " + term.bold_blue('https://github.com/jquast/x84/\r\n') + "\r\n\r\nAuthor: dingo, jojo\r\n" "IRC: #prsv on irc.efnet.org") output += pager.title(u'- about ' + term.blue('X/84') + u' -') else: output += pager.update( u' no information about %s.' % (entry or u'').title(), ) output += pager.title(u'- about ' + term.blue(entry or u'') + u' -') return output
def eol(self): """ Return True when no more input can be accepted (end of line). """ return len(Ansi(self.content)) >= self.max_length