Python - ncurses printing html as colors

From NoskeWiki
Jump to navigation Jump to search

About

NOTE: This page is a daughter page of: Ncurses and Python


ncurses is text-based user interface library. Here we build on examples and provide functions capable of taking a html string, and using it to change font color and style as it prints.

The goal.... instead of needing to break apart formatted text like this:

  screen.addstr(1, 0, 'Hello ', curses.A_NORMAL | curses.color_pair(1))
  screen.addstr(1, 6, 'bold', curses.A_BOLD | curses.color_pair(1))
  screen.addstr(1, 10, ' world', curses.A_NORMAL | curses.color_pair(1))
  screen.addstr(2, 0, 'See: ', curses.A_NORMAL | curses.color_pair(1))
  screen.addstr(2, 5, 'www.gnu.org', curses.A_UNDERLINED | curses.color_pair(8))

We want this:

  print_html_str(1, 0, 'Hello <b>bold</b> world<br>See: <a>www.gnu.org</a>', True)


ncurses_color_sub.py

ncurses_color_sub.py

#!/usr/bin/env python
# ncurses_color_sub.py - ncurses interface which inputs a HTML like
# string and outputs with color.
# See: http://andrewnoske.com/wiki/Python_-_ncurses_printing_html_as_colors
#
# Usage:
#   ncurses_color_sub.py
#
# Usage examples:
#   $ python ncurses_color_sub.py

import curses

demo_str = """<h1>Heading</h1>
Bold text: <b>bold is yellow</b>.
Blue hyperlink: <a>http://andrewnoske.com/wiki/</a>.
"""


def get_style_for_html_tag(tag_inner_str):
  """Inputs html tag label and returns matching font color/style for ncurses.

  Args:
    tag_inner_str: inner contents of a HTML tag, such as 'b' for a start bold tag.
        Not many tag types are supported and any unrecognized tags or closing
        tags (eg: '/b') return a default style - in this case normal white text.
 
  Returns:
    A curses font specification suitable for input into 'screen.addstr()'.
  """
  style_default = curses.A_NORMAL | curses.color_pair(7)  # White.
  style_b = curses.A_NORMAL | curses.color_pair(3)        # Yellow.
  style_h1 = curses.A_BOLD | curses.color_pair(6)         # Cyan bold.
  style_a = curses.A_UNDERLINE | curses.color_pair(4)     # Blue underlined.
  
  if tag_inner_str == 'b':
    return style_b
  elif tag_inner_str == 'h1':
    return style_h1
  elif tag_inner_str == 'a' or tag_inner_str.startswith('a '):
    return style_a
  else:
    return style_default


def print_html_str(y_start, x_start, html_str,
                   in_screen, make_newlines_br):
  """Prints to ncurses html where certain tags change the font color/style.

  Args:
    y_start: col position to start at.
    x_start: row position to start at.
    html_str: a html text string such as 'this <b>example</b>'.
    in_screen: an ncurses screen generated with 'curses.initscr()'
    make_newlines_br: if true will turn any newlines into '<br>' before
        processing otherwise newlines are simply ignored and only '<br>'
        or '<p>' will go to the next line.

  Returns:
    The number of lines (rows) output, which will be at least one, but more
    if there are '<br>' and/or '<p>' tags.
  """
  curr_color = get_style_for_html_tag('default')
  
  x = x_start
  y = y_start
  remaining_str = html_str.replace('\n', '')
  if make_newlines_br:
    remaining_str = html_str.replace('\n', '<br>')
  
  while len(remaining_str) > 0:
    pos = remaining_str.find('<')  # Find start of tag (<).
    if pos >= 0:
      # Output part up to start of tag:
      part_str = remaining_str[:pos]
      in_screen.addstr(y, x, part_str, curr_color)
      remaining_str = remaining_str[pos+1:]
      x = x + pos
      # Determine what tag is:
      pos_end = remaining_str.find('>')  # Find end of tag (<).
      if pos_end >= 0:
        tag_str = remaining_str[:pos_end]
        curr_color = get_style_for_html_tag(tag_str)
        remaining_str = remaining_str[pos_end+1:]
        if tag_str == 'br' or tag_str == 'p':
          y = y+1
          x = x_start
    else:
      # Output everything remaining:
      in_screen.addstr(y, x, remaining_str, curr_color)
      remaining_str = ''
      return y - y_start + 1

def create_color_pairs():
  """Creates a set of indexed color pairs for curses for each text color.
    
  Notice these are ordered 1-8 in rough order of 'ANSI color' values
  but with black as 8 instead of 0, since numbers start at 1.
  See: http://andrewnoske.com/wiki/Unix_-_ANSI_colors
  """
  curses.start_color()
  curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
  curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
  curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)
  curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK)
  curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
  curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK)
  curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK)
  curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_BLACK)

def main():
  screen = curses.initscr()
  create_color_pairs()
  screen.clear()
  screen.border(0)
  print_html_str(2, 5, 'Here is a <b>basic test</b>', screen, False)
  print_html_str(5, 5, demo_str, screen, True)
  screen.refresh()
  opt = screen.getch()  # Wait for user to enter character.
  curses.endwin()

if __name__ == '__main__':
  main()


See Also


Links