summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Quast <jquast@io.com>2015-04-15 00:32:57 -0700
committerJeff Quast <jquast@io.com>2015-04-15 00:32:57 -0700
commit244e24ff414312403f49eba1ef92d1b308e2c092 (patch)
tree7e18b098ef0969313f27719ed9a0b98f5aa78ac7
parent0ba17756a2292efde716e60255fea23e1d45a4d0 (diff)
downloadblessings-244e24ff414312403f49eba1ef92d1b308e2c092.tar.gz
Fix all bin/*.py and add docs/examples.rst
-rw-r--r--.prospector.yaml1
-rwxr-xr-xbin/editor.py256
-rwxr-xr-xbin/keymatrix.py173
-rwxr-xr-xbin/on_resize.py60
-rwxr-xr-xbin/progress_bar.py14
-rwxr-xr-xbin/tprint.py25
-rwxr-xr-xbin/worms.py216
-rw-r--r--docs/examples.rst68
-rw-r--r--docs/index.rst1
9 files changed, 491 insertions, 323 deletions
diff --git a/.prospector.yaml b/.prospector.yaml
index c32f978..e49da65 100644
--- a/.prospector.yaml
+++ b/.prospector.yaml
@@ -6,7 +6,6 @@ ignore-patterns:
- ^docs/
- ^build/
# ignore these, their quality does not so much matter.
- - ^bin/
- ^blessings/tests/
- ^tools/
# not maintained
diff --git a/bin/editor.py b/bin/editor.py
index dc8cde2..f3bebbd 100755
--- a/bin/editor.py
+++ b/bin/editor.py
@@ -1,152 +1,86 @@
-#!/usr/bin/env python3
-# Dumb full-screen editor. It doesn't save anything but to the screen.
-#
-# "Why wont python let me read memory
-# from screen like assembler? That's dumb." -hellbeard
-#
-# This program makes example how to deal with a keypad for directional
-# movement, with both numlock on and off.
+#!/usr/bin/env python
+"""
+A Dumb full-screen editor.
+
+This program demonstrates how to deal with the keypad for directional movement.
+
+It is a very dumb and naive full-screen editor. It can be saved to a file
+using ^S, which demonstrates using keystroke_input with parameter raw=True.
+"""
+# My favorite quote, from a former assembly programmer learning
+# python with blessings, "Why wont python let me read memory
+# from screen like assembler? That's dumb." -xzip!impure
+
from __future__ import division, print_function
import collections
import functools
+import string
from blessings import Terminal
-
-echo = lambda text: (
- functools.partial(print, end='', flush=True)(text))
-
-echo_yx = lambda cursor, text: (
- echo(cursor.term.move(cursor.y, cursor.x) + text))
+# python 2/3 compatibility, provide 'echo' function as an
+# alias for "print without newline and flush"
+try:
+ # pylint: disable=invalid-name
+ # Invalid constant name "echo"
+ echo = functools.partial(print, end='', flush=True)
+ echo(u'')
+except TypeError:
+ # TypeError: 'flush' is an invalid keyword argument for this function
+ import sys
+
+ def echo(text):
+ """Display ``text`` and flush output."""
+ sys.stdout.write(u'{}'.format(text))
+ sys.stdout.flush()
+
+def echo_yx(cursor, text):
+ """Move to ``cursor`` and display ``text``."""
+ echo(cursor.term.move(cursor.y, cursor.x) + text)
Cursor = collections.namedtuple('Point', ('y', 'x', 'term'))
-above = lambda csr, n: (
- Cursor(y=max(0, csr.y - n),
- x=csr.x,
- term=csr.term))
-
-below = lambda csr, n: (
- Cursor(y=min(csr.term.height - 1, csr.y + n),
- x=csr.x,
- term=csr.term))
-
-right_of = lambda csr, n: (
- Cursor(y=csr.y,
- x=min(csr.term.width - 1, csr.x + n),
- term=csr.term))
-
-left_of = lambda csr, n: (
- Cursor(y=csr.y,
- x=max(0, csr.x - n),
- term=csr.term))
-
-home = lambda csr: (
- Cursor(y=csr.y,
- x=0,
- term=csr.term))
-
-end = lambda csr: (
- Cursor(y=csr.y,
- x=csr.term.width - 1,
- term=csr.term))
-
-bottom = lambda csr: (
- Cursor(y=csr.term.height - 1,
- x=csr.x,
- term=csr.term))
-
-top = lambda csr: (
- Cursor(y=1,
- x=csr.x,
- term=csr.term))
-
-center = lambda csr: Cursor(
- csr.term.height // 2,
- csr.term.width // 2,
- csr.term)
-
-
-lookup_move = lambda inp_code, csr, term: {
- # arrows, including angled directionals
- csr.term.KEY_END: below(left_of(csr, 1), 1),
- csr.term.KEY_KP_1: below(left_of(csr, 1), 1),
-
- csr.term.KEY_DOWN: below(csr, 1),
- csr.term.KEY_KP_2: below(csr, 1),
-
- csr.term.KEY_PGDOWN: below(right_of(csr, 1), 1),
- csr.term.KEY_LR: below(right_of(csr, 1), 1),
- csr.term.KEY_KP_3: below(right_of(csr, 1), 1),
-
- csr.term.KEY_LEFT: left_of(csr, 1),
- csr.term.KEY_KP_4: left_of(csr, 1),
-
- csr.term.KEY_CENTER: center(csr),
- csr.term.KEY_KP_5: center(csr),
-
- csr.term.KEY_RIGHT: right_of(csr, 1),
- csr.term.KEY_KP_6: right_of(csr, 1),
-
- csr.term.KEY_HOME: above(left_of(csr, 1), 1),
- csr.term.KEY_KP_7: above(left_of(csr, 1), 1),
-
- csr.term.KEY_UP: above(csr, 1),
- csr.term.KEY_KP_8: above(csr, 1),
-
- csr.term.KEY_PGUP: above(right_of(csr, 1), 1),
- csr.term.KEY_KP_9: above(right_of(csr, 1), 1),
-
- # shift + arrows
- csr.term.KEY_SLEFT: left_of(csr, 10),
- csr.term.KEY_SRIGHT: right_of(csr, 10),
- csr.term.KEY_SDOWN: below(csr, 10),
- csr.term.KEY_SUP: above(csr, 10),
-
- # carriage return
- csr.term.KEY_ENTER: home(below(csr, 1)),
-}.get(inp_code, csr)
-
-
def readline(term, width=20):
- # a rudimentary readline function
- string = u''
+ """A rudimentary readline implementation."""
+ text = u''
while True:
- inp = term.inkey()
+ inp = term.keystroke()
if inp.code == term.KEY_ENTER:
break
elif inp.code == term.KEY_ESCAPE or inp == chr(3):
- string = None
+ text = None
break
- elif not inp.is_sequence and len(string) < width:
- string += inp
+ elif not inp.is_sequence and len(text) < width:
+ text += inp
echo(inp)
elif inp.code in (term.KEY_BACKSPACE, term.KEY_DELETE):
- string = string[:-1]
- echo('\b \b')
- return string
+ text = text[:-1]
+ echo(u'\b \b')
+ return text
def save(screen, fname):
+ """Save screen contents to file."""
if not fname:
return
- with open(fname, 'w') as fp:
+ with open(fname, 'w') as fout:
cur_row = cur_col = 0
for (row, col) in sorted(screen):
char = screen[(row, col)]
while row != cur_row:
cur_row += 1
cur_col = 0
- fp.write(u'\n')
+ fout.write(u'\n')
while col > cur_col:
cur_col += 1
- fp.write(u' ')
- fp.write(char)
+ fout.write(u' ')
+ fout.write(char)
cur_col += 1
- fp.write(u'\n')
+ fout.write(u'\n')
def redraw(term, screen, start=None, end=None):
+ """Redraw the screen."""
if start is None and end is None:
echo(term.clear)
start, end = (Cursor(y=min([y for (y, x) in screen or [(0, 0)]]),
@@ -169,20 +103,100 @@ def redraw(term, screen, start=None, end=None):
# just write past last one
echo(screen[row, col])
-
def main():
+ """Program entry point."""
+ above = lambda csr, n: (
+ Cursor(y=max(0, csr.y - n),
+ x=csr.x,
+ term=csr.term))
+
+ below = lambda csr, n: (
+ Cursor(y=min(csr.term.height - 1, csr.y + n),
+ x=csr.x,
+ term=csr.term))
+
+ right_of = lambda csr, n: (
+ Cursor(y=csr.y,
+ x=min(csr.term.width - 1, csr.x + n),
+ term=csr.term))
+
+ left_of = lambda csr, n: (
+ Cursor(y=csr.y,
+ x=max(0, csr.x - n),
+ term=csr.term))
+
+ home = lambda csr: (
+ Cursor(y=csr.y,
+ x=0,
+ term=csr.term))
+
+ end = lambda csr: (
+ Cursor(y=csr.y,
+ x=csr.term.width - 1,
+ term=csr.term))
+
+ bottom = lambda csr: (
+ Cursor(y=csr.term.height - 1,
+ x=csr.x,
+ term=csr.term))
+
+ center = lambda csr: Cursor(
+ csr.term.height // 2,
+ csr.term.width // 2,
+ csr.term)
+
+ lookup_move = lambda inp_code, csr, term: {
+ # arrows, including angled directionals
+ csr.term.KEY_END: below(left_of(csr, 1), 1),
+ csr.term.KEY_KP_1: below(left_of(csr, 1), 1),
+
+ csr.term.KEY_DOWN: below(csr, 1),
+ csr.term.KEY_KP_2: below(csr, 1),
+
+ csr.term.KEY_PGDOWN: below(right_of(csr, 1), 1),
+ csr.term.KEY_LR: below(right_of(csr, 1), 1),
+ csr.term.KEY_KP_3: below(right_of(csr, 1), 1),
+
+ csr.term.KEY_LEFT: left_of(csr, 1),
+ csr.term.KEY_KP_4: left_of(csr, 1),
+
+ csr.term.KEY_CENTER: center(csr),
+ csr.term.KEY_KP_5: center(csr),
+
+ csr.term.KEY_RIGHT: right_of(csr, 1),
+ csr.term.KEY_KP_6: right_of(csr, 1),
+
+ csr.term.KEY_HOME: above(left_of(csr, 1), 1),
+ csr.term.KEY_KP_7: above(left_of(csr, 1), 1),
+
+ csr.term.KEY_UP: above(csr, 1),
+ csr.term.KEY_KP_8: above(csr, 1),
+
+ csr.term.KEY_PGUP: above(right_of(csr, 1), 1),
+ csr.term.KEY_KP_9: above(right_of(csr, 1), 1),
+
+ # shift + arrows
+ csr.term.KEY_SLEFT: left_of(csr, 10),
+ csr.term.KEY_SRIGHT: right_of(csr, 10),
+ csr.term.KEY_SDOWN: below(csr, 10),
+ csr.term.KEY_SUP: above(csr, 10),
+
+ # carriage return
+ csr.term.KEY_ENTER: home(below(csr, 1)),
+ }.get(inp_code, csr)
+
term = Terminal()
csr = Cursor(0, 0, term)
screen = {}
with term.hidden_cursor(), \
- term.raw(), \
+ term.keystroke_input(raw=True), \
term.location(), \
term.fullscreen(), \
term.keypad():
inp = None
while True:
echo_yx(csr, term.reverse(screen.get((csr.y, csr.x), u' ')))
- inp = term.inkey()
+ inp = term.keystroke()
if inp == chr(3):
# ^c exits
@@ -191,8 +205,8 @@ def main():
elif inp == chr(19):
# ^s saves
echo_yx(home(bottom(csr)),
- term.ljust(term.bold_white('Filename: ')))
- echo_yx(right_of(home(bottom(csr)), len('Filename: ')), u'')
+ term.ljust(term.bold_white(u'Filename: ')))
+ echo_yx(right_of(home(bottom(csr)), len(u'Filename: ')), u'')
save(screen, readline(term))
echo_yx(home(bottom(csr)), term.clear_eol)
redraw(term=term, screen=screen,
@@ -210,7 +224,7 @@ def main():
echo_yx(csr, screen.get((csr.y, csr.x), u' '))
csr = n_csr
- elif not inp.is_sequence and inp.isprintable():
+ elif not inp.is_sequence and inp in string.printable:
echo_yx(csr, inp)
screen[(csr.y, csr.x)] = inp.__str__()
n_csr = right_of(csr, 1)
diff --git a/bin/keymatrix.py b/bin/keymatrix.py
index f4e99f6..2306641 100755
--- a/bin/keymatrix.py
+++ b/bin/keymatrix.py
@@ -1,91 +1,116 @@
#!/usr/bin/env python
-from __future__ import division
+"""
+A simple "game": hit all application keys to win.
+
+Display all known key capabilities that may match the terminal.
+As each key is pressed on input, it is lit up and points are scored.
+"""
+from __future__ import division, print_function
+import functools
import sys
from blessings import Terminal
+# python 2/3 compatibility, provide 'echo' function as an
+# alias for "print without newline and flush"
+try:
+ # pylint: disable=invalid-name
+ # Invalid constant name "echo"
+ echo = functools.partial(print, end='', flush=True)
+ echo(u'')
+except TypeError:
+ # TypeError: 'flush' is an invalid keyword argument for this function
+
+ def echo(text):
+ """Display ``text`` and flush output."""
+ sys.stdout.write(u'{}'.format(text))
+ sys.stdout.flush()
+
+def refresh(term, board, level, score, inps):
+ """Refresh the game screen."""
+ echo(term.home + term.clear)
+ level_color = level % 7
+ if level_color == 0:
+ level_color = 4
+ bottom = 0
+ for keycode, attr in board.items():
+ echo(u''.join((
+ term.move(attr['row'], attr['column']),
+ term.color(level_color),
+ (term.reverse if attr['hit'] else term.bold),
+ keycode,
+ term.normal)))
+ bottom = max(bottom, attr['row'])
+ echo(term.move(term.height, 0)
+ + 'level: %s score: %s' % (level, score,))
+ if bottom >= (term.height - 5):
+ sys.stderr.write(
+ ('\n' * (term.height // 2)) +
+ term.center(term.red_underline('cheater!')) + '\n')
+ sys.stderr.write(
+ term.center("(use a larger screen)") +
+ ('\n' * (term.height // 2)))
+ sys.exit(1)
+ echo(term.move(bottom + 1, 0))
+ echo('Press ^C to exit.')
+ for row, inp in enumerate(inps[(term.height - (bottom + 3)) * -1:], 1):
+ echo(term.move(bottom + row + 1, 0))
+ echo('{0!r}, {1}, {2}'.format(
+ inp.__str__() if inp.is_sequence else inp,
+ inp.code,
+ inp.name))
+ echo(term.clear_eol)
+
+def build_gameboard(term):
+ """Build the gameboard layout."""
+ column, row = 0, 0
+ board = dict()
+ spacing = 2
+ for keycode in sorted(term._keycodes.values()):
+ if (keycode.startswith('KEY_F')
+ and keycode[-1].isdigit()
+ and int(keycode[len('KEY_F'):]) > 24):
+ continue
+ if column + len(keycode) + (spacing * 2) >= term.width:
+ column = 0
+ row += 1
+ board[keycode] = {'column': column,
+ 'row': row,
+ 'hit': 0,
+ }
+ column += len(keycode) + (spacing * 2)
+ return board
+
+def add_score(score, pts, level):
+ """Add points to score, determine and return new score and level."""
+ lvl_multiplier = 10
+ score += pts
+ if 0 == (score % (pts * lvl_multiplier)):
+ level += 1
+ return score, level
+
def main():
- """
- Displays all known key capabilities that may match the terminal.
- As each key is pressed on input, it is lit up and points are scored.
- """
+ """Program entry point."""
term = Terminal()
score = level = hit_highbit = hit_unicode = 0
dirty = True
- def refresh(term, board, level, score, inp):
- sys.stdout.write(term.home + term.clear)
- level_color = level % 7
- if level_color == 0:
- level_color = 4
- bottom = 0
- for keycode, attr in board.items():
- sys.stdout.write(u''.join((
- term.move(attr['row'], attr['column']),
- term.color(level_color),
- (term.reverse if attr['hit'] else term.bold),
- keycode,
- term.normal)))
- bottom = max(bottom, attr['row'])
- sys.stdout.write(term.move(term.height, 0)
- + 'level: %s score: %s' % (level, score,))
- sys.stdout.flush()
- if bottom >= (term.height - 5):
- sys.stderr.write(
- ('\n' * (term.height // 2)) +
- term.center(term.red_underline('cheater!')) + '\n')
- sys.stderr.write(
- term.center("(use a larger screen)") +
- ('\n' * (term.height // 2)))
- sys.exit(1)
- for row, inp in enumerate(inps[(term.height - (bottom + 2)) * -1:]):
- sys.stdout.write(term.move(bottom + row+1))
- sys.stdout.write('%r, %s, %s' % (inp.__str__() if inp.is_sequence
- else inp, inp.code, inp.name, ))
- sys.stdout.flush()
-
- def build_gameboard(term):
- column, row = 0, 0
- board = dict()
- spacing = 2
- for keycode in sorted(term._keycodes.values()):
- if (keycode.startswith('KEY_F')
- and keycode[-1].isdigit()
- and int(keycode[len('KEY_F'):]) > 24):
- continue
- if column + len(keycode) + (spacing * 2) >= term.width:
- column = 0
- row += 1
- board[keycode] = {'column': column,
- 'row': row,
- 'hit': 0,
- }
- column += len(keycode) + (spacing * 2)
- return board
-
- def add_score(score, pts, level):
- lvl_multiplier = 10
- score += pts
- if 0 == (score % (pts * lvl_multiplier)):
- level += 1
- return score, level
-
- gb = build_gameboard(term)
+ gameboard = build_gameboard(term)
inps = []
- with term.raw(), term.keypad(), term.location():
- inp = term.inkey(timeout=0)
+ with term.keystroke_input(raw=True), term.keypad(), term.location():
+ inp = term.keystroke(timeout=0)
while inp != chr(3):
if dirty:
- refresh(term, gb, level, score, inps)
+ refresh(term, gameboard, level, score, inps)
dirty = False
- inp = term.inkey(timeout=5.0)
+ inp = term.keystroke(timeout=5.0)
dirty = True
if (inp.is_sequence and
- inp.name in gb and
- 0 == gb[inp.name]['hit']):
- gb[inp.name]['hit'] = 1
+ inp.name in gameboard and
+ 0 == gameboard[inp.name]['hit']):
+ gameboard[inp.name]['hit'] = 1
score, level = add_score(score, 100, level)
elif inp and not inp.is_sequence and 128 <= ord(inp) <= 255:
hit_highbit += 1
@@ -97,9 +122,9 @@ def main():
score, level = add_score(score, 100, level)
inps.append(inp)
- with term.cbreak():
- sys.stdout.write(term.move(term.height))
- sys.stdout.write(
+ with term.keystroke_input():
+ echo(term.move(term.height))
+ echo(
u'{term.clear_eol}Your final score was {score} '
u'at level {level}{term.clear_eol}\n'
u'{term.clear_eol}\n'
@@ -113,7 +138,7 @@ def main():
hit_highbit=hit_highbit,
hit_unicode=hit_unicode)
)
- term.inkey()
+ term.keystroke()
if __name__ == '__main__':
main()
diff --git a/bin/on_resize.py b/bin/on_resize.py
index 4140748..c81efe6 100755
--- a/bin/on_resize.py
+++ b/bin/on_resize.py
@@ -5,33 +5,43 @@ This is an example application for the 'blessings' Terminal library for python.
Window size changes are caught by the 'on_resize' function using a traditional
signal handler. Meanwhile, blocking keyboard input is displayed to stdout.
If a resize event is discovered, an empty string is returned by
-term.keystroke() when interruptable is False, as it is here.
+term.keystroke().
"""
+from __future__ import print_function
import signal
from blessings import Terminal
-
-term = Terminal()
-
-
-def on_resize(sig, action):
- # Its generally not a good idea to put blocking functions (such as print)
- # within a signal handler -- if another SIGWINCH is recieved while this
- # function blocks, an error will occur. In most programs, you'll want to
- # set some kind of 'dirty' flag, perhaps by a Semaphore or global variable.
- print('height={t.height}, width={t.width}\r'.format(t=term))
-
-signal.signal(signal.SIGWINCH, on_resize)
-
-# note that, a terminal driver actually writes '\r\n' when '\n' is found, but
-# in raw mode, we are allowed to write directly to the terminal without the
-# interference of such driver -- so we must write \r\n ourselves; as python
-# will append '\n' to our print statements, we simply end our statements with
-# \r.
-with term.raw():
- print("press 'X' to stop.\r")
- inp = None
- while inp != 'X':
- inp = term.inkey(interruptable=False)
- print(repr(inp) + u'\r')
+def main():
+ """Program entry point."""
+ term = Terminal()
+
+ def on_resize(*args):
+ # pylint: disable=unused-argument
+ # Unused argument 'args'
+
+ # Its generally not a good idea to put blocking functions (such as
+ # print) within a signal handler -- if another SIGWINCH is received
+ # while this function blocks, an error will occur.
+
+ # In most programs, you'll want to set some kind of 'dirty' flag,
+ # perhaps by a Semaphore like threading.Event or (thanks to the GIL)
+ # a simple global variable will suffice.
+ print('height={t.height}, width={t.width}\r'.format(t=term))
+
+ signal.signal(signal.SIGWINCH, on_resize)
+
+ # note that, a terminal driver actually writes '\r\n' when '\n' is found,
+ # but in raw mode, we are allowed to write directly to the terminal without
+ # the interference of such driver -- so we must write \r\n ourselves; as
+ # python will append '\n' to our print statements, we simply end our
+ # statements with \r.
+ with term.keystroke_input():
+ print("press 'X' to stop.\r")
+ inp = None
+ while inp != 'X':
+ inp = term.keystroke()
+ print(repr(inp) + u'\r')
+
+if __name__ == '__main__':
+ main()
diff --git a/bin/progress_bar.py b/bin/progress_bar.py
index 5257e95..cf9f09c 100755
--- a/bin/progress_bar.py
+++ b/bin/progress_bar.py
@@ -15,12 +15,13 @@ from blessings import Terminal
def main():
+ """Program entry point."""
term = Terminal()
assert term.hpa(1) != u'', (
'Terminal does not support hpa (Horizontal position absolute)')
col, offset = 1, 1
- with term.cbreak():
+ with term.keystroke_input():
inp = None
print("press 'X' to stop.")
sys.stderr.write(term.move(term.height, 0) + u'[')
@@ -30,11 +31,16 @@ def main():
offset = -1
elif col <= 1:
offset = 1
- sys.stderr.write(term.move_x(col) + u'.' if offset == -1 else '=')
+ sys.stderr.write(term.move_x(col))
+ if offset == -1:
+ sys.stderr.write(u'.')
+ else:
+ sys.stderr.write(u'=')
col += offset
- sys.stderr.write(term.move_x(col) + u'|\b')
+ sys.stderr.write(term.move_x(col))
+ sys.stderr.write(u'|\b')
sys.stderr.flush()
- inp = term.inkey(0.04)
+ inp = term.keystroke(0.04)
print()
if __name__ == '__main__':
diff --git a/bin/tprint.py b/bin/tprint.py
index c00c486..984febc 100755
--- a/bin/tprint.py
+++ b/bin/tprint.py
@@ -1,20 +1,25 @@
#!/usr/bin/env python
-
+"""A simple cmd-line tool for displaying FormattingString capabilities."""
+from __future__ import print_function
import argparse
-from blessings import Terminal
+def main():
+ """Program entry point."""
+ from blessings import Terminal
+ parser = argparse.ArgumentParser(
+ description='displays argument as specified style')
-parser = argparse.ArgumentParser(
- description='displays argument as specified style')
+ parser.add_argument('style', type=str, help='style formatter')
+ parser.add_argument('text', type=str, nargs='+')
-parser.add_argument('style', type=str, help='style formatter')
-parser.add_argument('text', type=str, nargs='+')
+ term = Terminal()
+ args = parser.parse_args()
-term = Terminal()
-args = parser.parse_args()
+ style = getattr(term, args.style)
-style = getattr(term, args.style)
+ print(style(' '.join(args.text)))
-print(style(' '.join(args.text)))
+if __name__ == '__main__':
+ main()
diff --git a/bin/worms.py b/bin/worms.py
index be46d3d..0c51a6e 100755
--- a/bin/worms.py
+++ b/bin/worms.py
@@ -16,14 +16,17 @@ from blessings import Terminal
# python 2/3 compatibility, provide 'echo' function as an
# alias for "print without newline and flush"
try:
+ # pylint: disable=invalid-name
+ # Invalid constant name "echo"
echo = partial(print, end='', flush=True)
- echo('begin.')
+ echo(u'')
except TypeError:
# TypeError: 'flush' is an invalid keyword argument for this function
import sys
- def echo(object):
- sys.stdout.write(u'{}'.format(object))
+ def echo(text):
+ """python 2 version of print(end='', flush=True)."""
+ sys.stdout.write(u'{0}'.format(text))
sys.stdout.flush()
# a worm is a list of (y, x) segments Locations
@@ -40,97 +43,132 @@ Direction = namedtuple('Direction', ('y', 'x',))
# these functions return a new Location instance, given
# the direction indicated by their name.
LEFT = (0, -1)
-left_of = lambda segment, term: Location(
- y=segment.y,
- x=max(0, segment.x - 1))
-
RIGHT = (0, 1)
-right_of = lambda segment, term: Location(
- y=segment.y,
- x=min(term.width - 1, segment.x + 1))
-
UP = (-1, 0)
-above = lambda segment, term: Location(
- y=max(0, segment.y - 1),
- x=segment.x)
-
DOWN = (1, 0)
-below = lambda segment, term: Location(
- y=min(term.height - 1, segment.y + 1),
- x=segment.x)
-
-# return a direction function that defines the new bearing for any matching
-# keyboard code of inp_code; otherwise, the function for the current bearing.
-next_bearing = lambda term, inp_code, bearing: {
- term.KEY_LEFT: left_of,
- term.KEY_RIGHT: right_of,
- term.KEY_UP: above,
- term.KEY_DOWN: below,
-}.get(inp_code,
- # direction function given the current bearing
- {LEFT: left_of,
- RIGHT: right_of,
- UP: above,
- DOWN: below}[(bearing.y, bearing.x)])
-
-
-# return new bearing given the movement f(x).
-change_bearing = lambda f_mov, segment, term: Direction(
- f_mov(segment, term).y - segment.y,
- f_mov(segment, term).x - segment.x)
-
-# direction-flipped check, reject traveling in opposite direction.
-bearing_flipped = lambda dir1, dir2: (
- (0, 0) == (dir1.y + dir2.y, dir1.x + dir2.x)
-)
-
-# returns True if `loc' matches any (y, x) coordinates,
-# within list `segments' -- such as a list composing a worm.
-hit_any = lambda loc, segments: loc in segments
-
-# same as above, but `locations' is also an array of (y, x) coordinates.
-hit_vany = lambda locations, segments: any(
- hit_any(loc, segments) for loc in locations)
-
-# returns True if segments are same position (hit detection)
-hit = lambda src, dst: src.x == dst.x and src.y == dst.y
-
-# returns new worm_length if current nibble is hit,
-next_wormlength = lambda nibble, head, worm_length: (
- worm_length + nibble.value if hit(head, nibble.location)
- else worm_length)
-
-# returns new speed if current nibble is hit,
-next_speed = lambda nibble, head, speed, modifier: (
- speed * modifier if hit(head, nibble.location)
- else speed)
-
-# when displaying worm head, show a different glyph for horizontal/vertical
-head_glyph = lambda direction: (u':' if direction in (left_of, right_of)
- else u'"')
-
-
-# provide the next nibble -- continuously generate a random new nibble so
-# long as the current nibble hits any location of the worm, otherwise
-# return a nibble of the same location and value as provided.
+
+def left_of(segment, term):
+ """Return Location left-of given segment."""
+ # pylint: disable=unused-argument
+ # Unused argument 'term'
+ return Location(y=segment.y,
+ x=max(0, segment.x - 1))
+
+def right_of(segment, term):
+ """Return Location right-of given segment."""
+ return Location(y=segment.y,
+ x=min(term.width - 1, segment.x + 1))
+
+def above(segment, term):
+ """Return Location above given segment."""
+ # pylint: disable=unused-argument
+ # Unused argument 'term'
+ return Location(
+ y=max(0, segment.y - 1),
+ x=segment.x)
+
+def below(segment, term):
+ """Return Location below given segment."""
+ return Location(
+ y=min(term.height - 1, segment.y + 1),
+ x=segment.x)
+
+def next_bearing(term, inp_code, bearing):
+ """
+ Return direction function for new bearing by inp_code.
+
+ If no inp_code matches a bearing direction, return
+ a function for the current bearing.
+ """
+ return {
+ term.KEY_LEFT: left_of,
+ term.KEY_RIGHT: right_of,
+ term.KEY_UP: above,
+ term.KEY_DOWN: below,
+ }.get(inp_code,
+ # direction function given the current bearing
+ {LEFT: left_of,
+ RIGHT: right_of,
+ UP: above,
+ DOWN: below}[(bearing.y, bearing.x)])
+
+
+def change_bearing(f_mov, segment, term):
+ """Return new bearing given the movement f(x)."""
+ return Direction(
+ f_mov(segment, term).y - segment.y,
+ f_mov(segment, term).x - segment.x)
+
+def bearing_flipped(dir1, dir2):
+ """
+ direction-flipped check.
+
+ Return true if dir2 travels in opposite direction of dir1.
+ """
+ return (0, 0) == (dir1.y + dir2.y, dir1.x + dir2.x)
+
+def hit_any(loc, segments):
+ """Return True if `loc' matches any (y, x) coordinates within segments."""
+ # `segments' -- a list composing a worm.
+ return loc in segments
+
+def hit_vany(locations, segments):
+ """Return True if any locations are found within any segments."""
+ return any(hit_any(loc, segments)
+ for loc in locations)
+
+def hit(src, dst):
+ """Return True if segments are same position (hit detection)."""
+ return src.x == dst.x and src.y == dst.y
+
+def next_wormlength(nibble, head, worm_length):
+ """Return new worm_length if current nibble is hit."""
+ if hit(head, nibble.location):
+ return worm_length + nibble.value
+ return worm_length
+
+def next_speed(nibble, head, speed, modifier):
+ """Return new speed if current nibble is hit."""
+ if hit(head, nibble.location):
+ return speed * modifier
+ return speed
+
+def head_glyph(direction):
+ """Return character for worm head depending on horiz/vert orientation."""
+ if direction in (left_of, right_of):
+ return u':'
+ return u'"'
+
+
def next_nibble(term, nibble, head, worm):
- l, v = nibble.location, nibble.value
- while hit_vany([head] + worm, nibble_locations(l, v)):
- l = Location(x=randrange(1, term.width - 1),
+ """
+ Provide the next nibble.
+
+ continuously generate a random new nibble so long as the current nibble
+ hits any location of the worm. Otherwise, return a nibble of the same
+ location and value as provided.
+ """
+ loc, val = nibble.location, nibble.value
+ while hit_vany([head] + worm, nibble_locations(loc, val)):
+ loc = Location(x=randrange(1, term.width - 1),
y=randrange(1, term.height - 1))
- v = nibble.value + 1
- return Nibble(l, v)
+ val = nibble.value + 1
+ return Nibble(loc, val)
-# generate an array of locations for the current nibble's location -- a digit
-# such as '123' may be hit at 3 different (y, x) coordinates.
def nibble_locations(nibble_location, nibble_value):
+ """Return array of locations for the current "nibble"."""
+ # generate an array of locations for the current nibble's location
+ # -- a digit such as '123' may be hit at 3 different (y, x) coordinates.
return [Location(x=nibble_location.x + offset,
y=nibble_location.y)
for offset in range(0, 1 + len('{}'.format(nibble_value)) - 1)]
def main():
+ """Program entry point."""
+ # pylint: disable=too-many-locals
+ # Too many local variables (20/15)
term = Terminal()
worm = [Location(x=term.width // 2, y=term.height // 2)]
worm_length = 2
@@ -149,7 +187,7 @@ def main():
modifier = 0.93
inp = None
- with term.hidden_cursor(), term.raw():
+ with term.hidden_cursor(), term.keystroke_input():
while inp not in (u'q', u'Q'):
# delete the tail of the worm at worm_length
@@ -176,10 +214,12 @@ def main():
if n_nibble != nibble:
# erase the old one, careful to redraw the nibble contents
# with a worm color for those portions that overlay.
- for (y, x) in nibble_locations(*nibble):
- echo(term.move(y, x) + (color_worm if (y, x) == head
- else color_bg)(u' '))
- echo(term.normal)
+ for (yloc, xloc) in nibble_locations(*nibble):
+ echo(u''.join((
+ term.move(yloc, xloc),
+ (color_worm if (yloc, xloc) == head
+ else color_bg)(u' '),
+ term.normal)))
# and draw the new,
echo(term.move(*n_nibble.location) + (
color_nibble('{}'.format(n_nibble.value))))
@@ -195,7 +235,7 @@ def main():
# wait for keyboard input, which may indicate
# a new direction (up/down/left/right)
- inp = term.inkey(speed)
+ inp = term.keystroke(timeout=speed)
# discover new direction, given keyboard input and/or bearing.
nxt_direction = next_bearing(term, inp.code, bearing)
@@ -203,8 +243,8 @@ def main():
# discover new bearing, given new direction compared to prev
nxt_bearing = change_bearing(nxt_direction, head, term)
- # disallow new bearing/direction when flipped (running into
- # oneself, fe. travelling left while traveling right)
+ # disallow new bearing/direction when flipped: running into
+ # oneself, for example traveling left while traveling right.
if not bearing_flipped(bearing, nxt_bearing):
direction = nxt_direction
bearing = nxt_bearing
diff --git a/docs/examples.rst b/docs/examples.rst
new file mode 100644
index 0000000..a0a46cb
--- /dev/null
+++ b/docs/examples.rst
@@ -0,0 +1,68 @@
+Examples
+========
+
+A few programs are provided with blessings to help interactively
+test the various API features, but also serve as examples of using
+blessings to develop various kinds of applications.
+
+These examples are not distributed with the package -- they are
+only available in the github repository. You can retrieve them
+by cloning the repository, or simply downloading the "raw" file
+link.
+
+editor.py
+---------
+https://github.com/erikrose/blessings/blob/master/bin/editor.py
+
+This program demonstrates using the directional keys and noecho input
+mode. It acts as a (very dumb) fullscreen editor, with support for
+saving a file, which demonstrates how to provide a line-editor
+rudimentary line-editor as well.
+
+keymatrix.py
+------------
+https://github.com/erikrose/blessings/blob/master/bin/editor.py
+
+This program displays a "gameboard" of all known special KEY_NAME
+constants. When the key is depressed, it is highlighted, as well
+as displaying the unicode sequence, integer code, and friendly-name
+of any key pressed.
+
+on_resize.py
+------------
+https://github.com/erikrose/blessings/blob/master/bin/on_resize.py
+
+This program installs a SIGWINCH signal handler, which detects
+screen resizes while also polling for input, displaying keypresses.
+
+This demonstrates how a program can react to screen resize events.
+
+progress_bar.py
+---------------
+https://github.com/erikrose/blessings/blob/master/bin/progress_bar.py
+
+This program demonstrates a simple progress bar. All text is written
+to stderr, to avoid the need to "flush" or emit newlines, and makes
+use of the move_x (hpa) capability to "overstrike" the display a
+scrolling progress bar.
+
+tprint.py
+---------
+https://github.com/erikrose/blessings/blob/master/bin/tprint.py
+
+This program demonstrates how users may customize FormattingString
+styles. Accepting a string style, such as "bold" or "bright_red"
+as the first argument, all subsequent arguments are displayed by
+the given style. This shows how a program could provide
+user-customizable compound formatting names to configure a program's
+styling.
+
+worms.py
+--------
+https://github.com/erikrose/blessings/blob/master/blessings/editor.py
+
+This program demonstrates how an interactive game could be made
+with blessings. It is designed after the class game of "worms"
+that was made available for free with QBASIC, and later more
+popularly known as "snake" as it was named on early mobile
+platforms.
diff --git a/docs/index.rst b/docs/index.rst
index 37794b3..17e46ab 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -10,6 +10,7 @@ Contents:
intro
overview
+ examples
further
pains
api