Commits

Dinu Gherman committed 4b30809

Initial checkin of version 0.8.0 after moving to Mercurial.

Comments (0)

Files changed (16)

+syntax: glob
+.DS_Store
+*.pyc
+README.txt
+setup.py
+src/test_typotoo.py
+src/typotoo.py
+src/samples/de_hoelderlin-utf8.txt
+src/samples/diacritics-isolatin1.txt
+src/samples/diacritics-utf8.txt
+src/samples/en_hhgttg-utf8.txt
+src/samples/eo_zamenhof-utf8.txt
+src/samples/fr_balzac-utf8.txt
+include src/samples/*.txt
+TYPOTOO
+=======
+
+A command-line typing tutor for accelerating typing speed.
+
+
+ABOUT
+-----
+
+This is a simple command-line typing tutor with instant feedback for learning 
+to minimize typing errors and increase typing speed. It does not care which 
+fingers you use to type certain certain letters.
+
+This tool presents to the user some text to type in line by line. During 
+typing the number of right and wrong keystrokes, as well as the time needed 
+is recorded (time is measured between the first and last keystroke within
+each line). After each line some accumulated statistics is printed, inclu-
+ding total and relative right/wrong key counts, plus the typing speed. Keys 
+like BACKSPACE, TAB, ESCAPE, RETURN, and arrow keys are disabled during 
+typing, i.e. you cannot correct what you typed. CTRL-C should work, though.
+
+The default encoding used both, for the terminal in which you type and the 
+input file, is UTF-8. 
+
+Options include a case-insensitive mode, an accustic signal after each
+wrong keystroke, etc...
+
+
+USING
+-----
+
+From the system command-line you use 'typotoo.py' e.g. like this::
+
+  $ python typotoo.py --test
+  typotoo.py - simple command-line typing tutor
+  Type in the displayed text, or CTRL-C to quit.
+
+  the quick brown fox jumped over the lazy old dog
+  ++++++++++++++++++++++++++++++++++++-+-++++++-++
+  +:45 (93 %), -:3 (6 %) - all: 48 chars in 13.6 sec -> 212 chars/min
+  
+
+FEATURES
+--------
+
+- take input text files and present them line by line on the console
+- get user input from the console and compare with input file
+- output accumulated statistics on correctly and incorrectly typed characters
+- output accumulated statistics on typing speed
+- output beep for incorrectly typed letters
+- allow different text encodings for used terminal and input file
+- install a Python command-line script 'typotoo.py'
+
+
+TODO
+----
+
+- test on systems other than Mac OS X (more on Windows and Linux)
+- add logging capability
+
+
+INSTALLING
+----------
+
+::
+
+  python setup.py build
+  python setup.py install
+
+This will install a single Python script named 'typotoo.py' in your 'bin' 
+directory, usually in '/usr/local/bin'.
+
+
+TESTING
+-------
+
+typotoo.py was mainly developped and tested on Mac OS X, but it should also
+work on other Unices. I tested it a little bit on Linux and it seems to work
+out of the box using the default UTF-8 encodings. 
+
+Since testing this script involves the terminal and its settings (which are
+sometimes difficult or impossible to obtain) it is not that easy to build a
+Unittest, say, so there is no 'normal' test suite included in this source code 
+distribution. 
+
+But the source contains a Python script named test_typotoo.py that is a tiny 
+version of typotoo.py. It also iterates over some input file and compares its 
+letters with those typed in on the keyboard, but prints different codes for 
+each letter as well. It should be helpful in debugging typotoo.py on other 
+platforms like Windows.
+
+Dinu Gherman
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+name = 'cookiefil'
+version = '0.3.0'
+date = "2007-07-30"
+description = 'List or remove HTTP cookies stored in browser cookie files.'
+long_description = """\
+This is a tool for listing and removing HTTP cookies, e.g. in files like 
+~/Library/Cookies/Cookies.plist (on OS X)."""
+author = 'Dinu Gherman'
+author_email = '@'.join(['gherman', 'darwin.in-berlin.de'])
+maintainer = author
+maintainer_email = author_email
+license_short = 'GNU GPL'
+license = 'GNU General Public License (GPL)'
+platforms = ['MacOS X']
+keywords = ['Filter', 'HTTP', 'Cookie']
+url = 'http://www.python.net/~gherman'
+download_url = 'http://www.python.net/~gherman/tmp/cookiefil-0.3.tar.gz'
+package_dir = {'cookiefil': 'src/cookiefil'}
+package_data = {'cookiefil.test': ["*.plist", "*.txt", "*.xml"]}
+packages = ['cookiefil', 'cookiefil.test']
+scripts = ['src/scripts/cookiefilter.py']
+classifiers = [
+    'Development Status :: 3 - Alpha',
+    'Environment :: Console',
+    'Environment :: Web Environment',
+    'Intended Audience :: End Users/Desktop',
+    'Intended Audience :: System Administrators',
+    'License :: OSI Approved :: GNU General Public License (GPL)',
+    'Operating System :: MacOS :: MacOS X',
+    'Operating System :: POSIX',
+    'Programming Language :: Python',
+    'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+    'Topic :: Security',
+    'Topic :: System :: Logging',
+    'Topic :: System :: Monitoring',
+    'Topic :: System :: Systems Administration',
+    'Topic :: Text Processing :: Markup :: XML',
+    'Topic :: Utilities',
+  ]

samples/de_hoelderlin-utf8.txt

+"Ich kann kein Volk mir denken, das zerrissner wäre, wie die Deutschen.
+Handwerker siehst du, aber keine Menschen, Denker, aber keine Menschen,
+Priester, aber keine Menschen, Herrn und Knechte, Jungen und gesetzte 
+Leute, aber keine Menschen - ist das nicht, wie ein Schlachtfeld, 
+wo Hände und Arme und alle Glieder zerstückelt untereinander liegen, 
+indessen das vergossne Lebensblut im Sande zerrinnt?"
+
+(Friedrich Hölderlin, Hyperion) 

samples/diacritics-isolatin1.txt

+�b�d�fgh�jklmn�p

samples/diacritics-utf8.txt

+ábçdèfghïjklmnöp

samples/en_hhgttg-utf8.txt

+"Far out in the uncharted backwaters of the unfashionable end of the western
+spiral arm of the Galaxy lies a small, unregarded yellow sun. Orbiting this at
+a distance of roughly ninety-eight million miles is an utterly insignificant
+little blue-green planet whose ape-descended life forms are so amazingly
+primitive that they still think digital watches are a pretty neat idea."
+
+(Douglas Adams, The Hitchhiker's Guide to the Galaxy)

samples/eo_zamenhof-utf8.txt

+"Mi forĝis, kiel forĝisto aŭ seruristo, la ŝlosilon de l'mondo. 
+Ĝin prenu per manoj kaj malfermadu la barilojn al la homaj koroj. 
+Tiel vi ŝanĝos la mondon."
+
+(Ludoviko Lazaro Zamenhof)

samples/fr_balzac-utf8.txt

+"Mais Paris est un véritable océan. Jetez-y la sonde, vous n'en connaîtrez 
+jamais la profondeur. Parcourez-le, décrivez-le : quelque soin que vous 
+mettiez à le parcourir, à le décrire ; quelques nombreux et intéressés que 
+soient les explorateurs de cette mer, il s'y rencontrera toujours un lieu 
+vierge, un antre inconnu, des fleurs, des perles, des monstres, quelque 
+chose d'inouï, oublié par les plongeurs littéraires."
+
+(Honoré de Balzac, dans La Comédie humaine, III, par Le Père Goriot)
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+from distutils.core import setup
+
+setup(
+    name = 'typotoo',
+    version = '0.8.0',
+    date = "2007-08-10",
+    description = 'A command-line typing tutor for accelerating typing speed.',
+    long_description = """\
+    This is a simple command-line typing tutor with immediate feedback for 
+learning to minimize typing errors and increase typing speed.""",
+    author = 'Dinu Gherman',
+    author_email = '@'.join(['gherman', 'darwin.in-berlin.de']),
+    maintainer = 'Dinu Gherman',
+    maintainer_email = '@'.join(['gherman', 'darwin.in-berlin.de']),
+    license = 'GNU General Public License (GPL), version 3',
+    platforms = ['Unix', 'Windows'],
+    keywords = ['Typing', 'Tutor'],
+    url = 'http://www.python.net/~gherman',
+    download_url = 'http://www.python.net/~gherman/tmp/typotoo-0.8.0.tar.gz',
+    package_dir = {"": "src"},
+    py_modules = ['typotoo', 'test_typotoo'],
+    scripts = ['src/typotoo.py'],
+    classifiers = [
+        'Development Status :: 3 - Alpha',
+        'Environment :: Console',
+        'Intended Audience :: End Users/Desktop',
+        'License :: OSI Approved :: GNU General Public License (GPL)',
+        'Operating System :: MacOS :: MacOS X',
+        # 'Operating System :: POSIX',
+        # 'Operating System :: Windows',
+        'Programming Language :: Python',
+        'Topic :: Utilities',
+      ]
+)

src/test_typotoo.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+
+import os, sys, getopt, locale
+
+
+# constants
+
+CTRL_X = chr(3)
+BELL = chr(7)
+TAB = chr(9)
+RETURN = chr(10)
+ESCAPE = chr(27)
+BACKSPACE = chr(127)
+
+
+# C-like getch and putch functions
+
+def getch_unix():
+    "C-like getch() from stdin with suppressed echo, UNIX-style."
+
+    fd = sys.stdin.fileno()
+    if os.isatty(fd):
+        old = termios.tcgetattr(fd)
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] & termios.ICANON & termios.ECHO
+        #new[6][termios.VMIN] = 1
+        #new[6][termios.VTIME] = 0
+        try:
+            termios.tcsetattr(fd, termios.TCSANOW, new)
+            ch = os.read(fd, 4)
+        finally:
+            termios.tcsetattr(fd, termios.TCSAFLUSH, old)
+    else:
+        ch = os.read(fd, 4)
+
+    return(ch)
+
+
+def putch_unix(ch):
+    "C-like putch() to stdout, UNIX-style."
+
+    fd = sys.stdout.fileno()
+    if os.isatty(fd):
+        old = termios.tcgetattr(fd)
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] & termios.ICANON & termios.ECHO
+        #new[6][termios.VMIN] = 1
+        #new[6][termios.VTIME] = 0
+        try:
+            termios.tcsetattr(fd, termios.TCSANOW, new)
+            os.write(fd, ch)
+        finally:
+            termios.tcsetattr(fd, termios.TCSAFLUSH, old)
+    else:
+        ch = os.read(fd, ch)
+
+
+def getch_win():
+    "C-like getch() from stdin with suppressed echo, Windows-style."
+
+    return msvcrt.getch()
+
+
+def putch_win(ch):
+    "C-like putch() to stdout, Windows-style."
+
+    msvcrt.putch(ch)
+
+
+# set Unix or Windows implementation for getch/putch
+if sys.platform[:3] == "win":
+    import msvcrt
+    getch, putch = getch_win, putch_win
+else:
+    import termios
+    getch, putch = getch_unix, putch_unix
+
+
+def testEncoding(bytes, encoding):
+    "Test if some bytes are encoded in a given encoding (roundtrip test)."
+	
+    try: dec = bytes.decode(encoding)
+    except: return False
+
+    try: enc = dec.encode(encoding)
+    except: return False
+
+    return bytes == enc
+
+
+def _test(path=None, terminal_encoding="utf8", file_encoding="utf8"):
+    print "Press any key (CTRL-X to stop)."
+
+    txt = ""
+    if path != None:
+        raw = open(path).read()
+        # print "**", testEncoding(raw, "latin1")
+        txt = raw.decode(file_encoding)
+
+    i = 0
+    while True:
+        if path != None and i > len(txt)-1:
+            break
+        if path != None:
+            chi = txt[i]
+            chj = chi.encode(terminal_encoding)
+            print "expected:", "'%s'" % chj, [ord(ki) for ki in chj]
+            i += 1
+
+        k = getch()
+        print "***", k
+        decArr = [ord(ki) for ki in k if ord(ki) != 22]
+        hexArr = [str(hex(ord(ki))) for ki in k if ord(ki) != 22]
+        ch = k
+        if k == CTRL_X:
+            sys.exit()
+        else: # if len(k) == 4:
+            ch = "".join(map(chr, decArr))
+        format = "received: '%s', len:%d, ord:%s, hex:%s"
+        args = ch, len(k), decArr, hexArr
+        print format % args
+
+        if path != None:
+            print "equal:", map(ord, chj), map(ord, ch)
+            print
+
+
+if __name__=='__main__':
+    print "default encoding:", sys.getdefaultencoding()
+    print "prefer. encoding:", locale.getpreferredencoding()
+    print "default locale:  ", locale.getdefaultlocale()
+    print
+
+    try:
+        shortOpts = ""
+        longOpts = ("terminal_encoding=", "file_encoding=")
+        opts, args = getopt.getopt(sys.argv[1:], shortOpts, longOpts)
+    except getopt.GetoptError:
+       print "Error"
+       sys.exit()
+
+    # overwrite default options            
+    terminal_encoding = "utf8"
+    file_encoding = "utf8"
+    for k, v in opts:
+        if k == '--terminal_encoding': 
+            terminal_encoding = v
+        elif k == '--file_encoding': 
+            file_encoding = v
+
+    try:
+        path = args[0]
+    except IndexError:
+        path = None
+
+    _test(path, 
+        terminal_encoding=terminal_encoding, 
+        file_encoding=file_encoding)

src/test_typotoo30.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+
+import os, sys, getopt, locale
+
+
+# constants
+
+CTRL_X = chr(3)
+BELL = chr(7)
+TAB = chr(9)
+RETURN = chr(10)
+ESCAPE = chr(27)
+BACKSPACE = chr(127)
+
+
+# C-like getch and putch functions
+
+def getch_unix():
+    "C-like getch() from stdin with suppressed echo, UNIX-style."
+
+    fd = sys.stdin.fileno()
+    if os.isatty(fd):
+        old = termios.tcgetattr(fd)
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] & termios.ICANON & termios.ECHO
+        #new[6][termios.VMIN] = 1
+        #new[6][termios.VTIME] = 0
+        try:
+            termios.tcsetattr(fd, termios.TCSANOW, new)
+            ch = os.read(fd, 4)
+        finally:
+            termios.tcsetattr(fd, termios.TCSAFLUSH, old)
+    else:
+        ch = os.read(fd, 4)
+
+    return(ch)
+
+
+def putch_unix(ch):
+    "C-like putch() to stdout, UNIX-style."
+
+    fd = sys.stdout.fileno()
+    if os.isatty(fd):
+        old = termios.tcgetattr(fd)
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] & termios.ICANON & termios.ECHO
+        #new[6][termios.VMIN] = 1
+        #new[6][termios.VTIME] = 0
+        try:
+            termios.tcsetattr(fd, termios.TCSANOW, new)
+            os.write(fd, ch)
+        finally:
+            termios.tcsetattr(fd, termios.TCSAFLUSH, old)
+    else:
+        ch = os.read(fd, ch)
+
+
+def getch_win():
+    "C-like getch() from stdin with suppressed echo, Windows-style."
+
+    return msvcrt.getch()
+
+
+def putch_win(ch):
+    "C-like putch() to stdout, Windows-style."
+
+    msvcrt.putch(ch)
+
+
+# set Unix or Windows implementation for getch/putch
+if sys.platform[:3] == "win":
+    import msvcrt
+    getch, putch = getch_win, putch_win
+else:
+    import termios
+    getch, putch = getch_unix, putch_unix
+
+
+def testEncoding(bytes, encoding):
+    "Test if some bytes are encoded in a given encoding (roundtrip test)."
+	
+    try: dec = bytes.decode(encoding)
+    except: return False
+
+    try: enc = dec.encode(encoding)
+    except: return False
+
+    return bytes == enc
+
+
+def _test(path=None, terminal_encoding="utf8", file_encoding="utf8"):
+    print("Press any key (CTRL-X to stop).")
+
+    txt = ""
+    if path != None:
+        raw = open(path).read()
+        # print "**", testEncoding(raw, "latin1")
+        txt = raw.decode(file_encoding)
+
+    i = 0
+    while True:
+        if path != None and i > len(txt)-1:
+            break
+        if path != None:
+            chi = txt[i]
+            chj = chi.encode(terminal_encoding)
+            print("expected:", "'%s'" % chj, [ord(ki) for ki in chj])
+            i += 1
+
+        k = getch()
+        k = k.decode("utf-8")
+        print("***", k)
+        decArr = [ord(ki) for ki in k if ord(ki) != 22]
+        hexArr = [str(hex(ord(ki))) for ki in k if ord(ki) != 22]
+        ch = k
+        if k == CTRL_X:
+            sys.exit()
+        else: # if len(k) == 4:
+            ch = "".join(map(chr, decArr))
+        format = "received: '%s', len:%d, ord:%s, hex:%s"
+        args = ch, len(k), decArr, hexArr
+        print(format % args)
+
+        if path != None:
+            print("equal:", list(map(ord, chj)), list(map(ord, ch)))
+            print()
+
+
+if __name__=='__main__':
+    print("default encoding:", sys.getdefaultencoding())
+    print("prefer. encoding:", locale.getpreferredencoding())
+    print("default locale:  ", locale.getdefaultlocale())
+    print()
+
+    try:
+        shortOpts = ""
+        longOpts = ("terminal_encoding=", "file_encoding=")
+        opts, args = getopt.getopt(sys.argv[1:], shortOpts, longOpts)
+    except getopt.GetoptError:
+       print("Error")
+       sys.exit()
+
+    # overwrite default options            
+    terminal_encoding = "utf8"
+    file_encoding = "utf8"
+    for k, v in opts:
+        if k == '--terminal_encoding': 
+            terminal_encoding = v
+        elif k == '--file_encoding': 
+            file_encoding = v
+
+    try:
+        path = args[0]
+    except IndexError:
+        path = None
+
+    _test(path, 
+        terminal_encoding=terminal_encoding, 
+        file_encoding=file_encoding)
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+"""A command-line typing tutor.
+
+This is a simple command-line typing tutor with immediate feedback for learning 
+to minimize typing errors and increase typing speed. This tool does not care 
+which fingers you use to type certain certain letters.
+
+For more information please read the file README.txt.
+"""
+
+
+import os, sys, getopt, types, time
+
+
+# meta info
+
+__version__ = '0.8.0'
+__date__ = '2007-08-12'
+__license__ = 'GPL'
+__author__ = 'Dinu Gherman'
+__author_email__ = '@'.join(['gherman', 'darwin.in-berlin.de'])
+
+
+# constants
+
+CTRL_X = chr(3)
+BELL = chr(7)
+TAB = chr(9)
+RETURN = chr(10)
+ESCAPE = chr(27)
+BACKSPACE = chr(127)
+
+RIGHT_KEY_CHAR = '+'
+WRONG_KEY_CHAR = '-'
+
+
+# C-like getch and putch functions
+
+def getch_unix():
+    "C-like getch() from stdin with suppressed echo, UNIX-style."
+
+    fd = sys.stdin.fileno()
+    if os.isatty(fd):
+        old = termios.tcgetattr(fd)
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] & termios.ICANON & termios.ECHO
+        #new[6][termios.VMIN] = 1
+        #new[6][termios.VTIME] = 0
+        try:
+            termios.tcsetattr(fd, termios.TCSANOW, new)
+            ch = os.read(fd, 4)
+        finally:
+            termios.tcsetattr(fd, termios.TCSAFLUSH, old)
+    else:
+        ch = os.read(fd, 4)
+
+    return ch
+
+
+def putch_unix(ch):
+    "C-like putch() to stdout, UNIX-style."
+
+    fd = sys.stdout.fileno()
+    if os.isatty(fd):
+        old = termios.tcgetattr(fd)
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] & termios.ICANON & termios.ECHO
+        #new[6][termios.VMIN] = 1
+        #new[6][termios.VTIME] = 0
+        try:
+            termios.tcsetattr(fd, termios.TCSANOW, new)
+            os.write(fd, ch)
+        finally:
+            termios.tcsetattr(fd, termios.TCSAFLUSH, old)
+    else:
+        ch = os.read(fd, ch)
+
+
+def getch_win():
+    "C-like getch() from stdin with suppressed echo, Windows-style."
+
+    return msvcrt.getch()
+
+
+def putch_win(ch):
+    "C-like putch() to stdout, Windows-style."
+
+    msvcrt.putch(ch)
+
+
+# set Unix or Windows implementation for getch/putch
+
+if sys.platform[:3] == "win":
+    import msvcrt
+    getch, putch = getch_win, putch_win
+else:
+    import termios
+    getch, putch = getch_unix, putch_unix
+
+
+# classes (unused)
+
+class ConsoleTypingTutor:
+    def __init__(self): pass
+    def setFile(self, path): pass
+    def printStats(self): pass
+    def outLine(self, line): pass
+    def outText(self, text): pass
+
+
+# functions
+
+def printStats(right, wrong, dt):
+    """Print statistics for counters and elapsed time and typing speed."""
+
+    total = right + wrong
+    r, w, t = right, wrong, total
+    RKC, WKC = RIGHT_KEY_CHAR, WRONG_KEY_CHAR
+    args = (r, 100.*r/t, w, 100.*w/t, t, dt, t/dt*60.)
+    format = "%s:%%d (%%d %%%%), %s:%%d (%%d %%%%)" % (RKC, WKC)
+    format += " - all: %d chars in %4.1f sec -> %d chars/min" 
+    print format % args
+
+
+def outLine(line, withCase=True, sound=True, terminal_encoding="utf8", file_encoding="utf8"):
+    """Output one text line and collect data from user while typing it in.
+    Returns (right, wrong, time) tuple."""
+
+    if type(line) == types.StringType:
+        print line
+    elif type(line) == types.UnicodeType:
+        print line.encode(terminal_encoding)
+
+    taboo = (RETURN, BACKSPACE, TAB, ESCAPE)
+    right, wrong = 0, 0
+    started = False
+    for i in range(len(line)):
+        while True:
+            k = getch()
+            if k == CTRL_X:
+                print
+                sys.exit(1)
+            elif len(k) in (2, 4):
+                break
+            elif len(k) == 1 and k not in taboo:
+                break
+
+        # set t0 only after first accepted key (right or wrong) was hit
+        if started == False:
+            t0 = time.time()
+            started = True
+
+        ch = k
+        if len(k) in (2, 4):
+            decArr = [ord(ki) for ki in k if ord(ki) != 22] # skip ESC(?)
+            ch = "".join(map(chr, decArr)).decode(terminal_encoding)
+
+        if withCase and ch == line[i]:
+            right += 1
+            putch(RIGHT_KEY_CHAR)
+        elif not withCase and ch.upper() == line[i].upper(): 
+            right += 1
+            putch(RIGHT_KEY_CHAR)
+        else:
+            wrong += 1
+            putch(WRONG_KEY_CHAR)
+            if sound:
+                putch(BELL)
+
+    t = time.time() - t0
+    print 
+
+    return right, wrong, t
+
+
+def outText(text, withCase=True, terminal_encoding="utf8", file_encoding="utf8"):
+    "Output text line by line with some statistics after each line."
+
+    totalRight, totalWrong, totalTime = 0, 0, 0
+    for line in text.split('\n'):
+        line = line.strip()
+        # skipt empty lines
+        if len(line) == 0:
+            continue
+        right, wrong, t = outLine(line, withCase=withCase,
+            terminal_encoding=terminal_encoding, 
+            file_encoding=file_encoding)
+        totalRight += right
+        totalWrong += wrong
+        totalTime += t
+        printStats(totalRight, totalWrong, totalTime)
+        print
+
+
+# private
+
+def _showShortInfo():
+    "Show short program information."
+
+    name = os.path.basename(sys.argv[0])
+    format = "%s - simple command-line typing tutor"
+    print format % name
+    print "Type in the displayed text, or CTRL-C to quit."
+    print
+
+
+def _showUsage(msg=''):
+    "Show custom message or correct command-line usage and exit."
+
+    dict = {'name': os.path.basename(sys.argv[0]),
+            'version': __version__}
+    default = """\
+%(name)s v. %(version)s - simple command-line typing tutor
+(c) Dinu C. Gherman, 2005. Licensed under the GPL.
+Usage: %(name)s [options] <file.txt> 
+Options:  -h, --help           this message
+          -t, --test           test mode with predefined text
+          -i                   ignore case
+          --file_encoding      text encoding of input text file
+          --terminal_encoding  text encoding of typing terminal
+Samples:  %(name)s mytext.txt
+          %(name)s -t""" % dict
+
+    print msg or default
+    sys.exit(1)
+
+
+def _main():
+    # set defaults for options
+    debug = False
+    help, version = False, False
+    test, ignore = False, True
+    terminal_encoding = "utf8"
+    file_encoding = "utf8"
+
+    # get options and arguments from command-line
+    try:
+        shortOpts = "hvtid"
+        longOpts = ("help", "debug", "test", 
+            "terminal_encoding=", "file_encoding=")
+        opts, args = getopt.getopt(sys.argv[1:], shortOpts, longOpts)
+    except getopt.GetoptError:
+       _showUsage()
+
+    # overwrite default options
+    for k, v in opts:
+        if k in ('-d', '--debug'): 
+            debug = True
+        elif k in ('-h', '--help'): 
+            help = True
+        elif k == '--terminal_encoding': 
+            terminal_encoding = v
+        elif k == '--file_encoding': 
+            file_encoding = v
+        elif k == '-v': 
+            verbose = True
+        elif k in ('-t', '--test'):
+            test = True
+        elif k == '-i':
+            ignore = False
+
+    # perform appropriate action
+    if debug:
+       print opts, args
+    if help:
+       _showUsage()
+    if test == True:
+        _showShortInfo()
+        text = u"""the quick brown fox jumped over the lazy old dog"""
+        outText(text, withCase=ignore, 
+            terminal_encoding=terminal_encoding, 
+            file_encoding=file_encoding)
+        sys.exit(0)
+
+    if not args:
+       _showUsage()
+    else:
+        path = args[0]
+        try:
+            text = open(path).read().decode(file_encoding)
+        except UnicodeDecodeError:
+            msg = "Error: Could not decode file using '%s' codec."
+            print msg % file_encoding
+            sys.exit(0)
+        else:
+            _showShortInfo()
+            outText(text, withCase=ignore, 
+                terminal_encoding=terminal_encoding, 
+                file_encoding=file_encoding)
+
+
+if __name__=='__main__':
+    _main()
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+"""A command-line typing tutor.
+
+This is a simple command-line typing tutor with immediate feedback for learning 
+to minimize typing errors and increase typing speed. This tool does not care 
+which fingers you use to type certain certain letters.
+
+For more information please read the file README.txt.
+"""
+
+
+import os, sys, getopt, types, time
+
+
+# meta info
+
+__version__ = '0.8.0'
+__date__ = '2007-08-12'
+__license__ = 'GPL'
+__author__ = 'Dinu Gherman'
+__author_email__ = '@'.join(['gherman', 'darwin.in-berlin.de'])
+
+
+# constants
+
+CTRL_X = chr(3)
+BELL = chr(7)
+TAB = chr(9)
+RETURN = chr(10)
+ESCAPE = chr(27)
+BACKSPACE = chr(127)
+
+RIGHT_KEY_CHAR = '+'
+WRONG_KEY_CHAR = '-'
+
+
+# C-like getch and putch functions
+
+def getch_unix():
+    "C-like getch() from stdin with suppressed echo, UNIX-style."
+
+    fd = sys.stdin.fileno()
+    if os.isatty(fd):
+        old = termios.tcgetattr(fd)
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] & termios.ICANON & termios.ECHO
+        #new[6][termios.VMIN] = 1
+        #new[6][termios.VTIME] = 0
+        try:
+            termios.tcsetattr(fd, termios.TCSANOW, new)
+            ch = os.read(fd, 4)
+        finally:
+            termios.tcsetattr(fd, termios.TCSAFLUSH, old)
+    else:
+        ch = os.read(fd, 4)
+
+    ch = ch.decode("utf-8")
+
+    return ch
+
+
+def putch_unix(ch):
+    "C-like putch() to stdout, UNIX-style."
+
+    fd = sys.stdout.fileno()
+    if os.isatty(fd):
+        old = termios.tcgetattr(fd)
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] & termios.ICANON & termios.ECHO
+        #new[6][termios.VMIN] = 1
+        #new[6][termios.VTIME] = 0
+        try:
+            termios.tcsetattr(fd, termios.TCSANOW, new)
+            ch = ch.encode("utf-8")
+            os.write(fd, ch)
+        finally:
+            termios.tcsetattr(fd, termios.TCSAFLUSH, old)
+    else:
+        ch = os.read(fd, ch)
+
+
+def getch_win():
+    "C-like getch() from stdin with suppressed echo, Windows-style."
+
+    return msvcrt.getch()
+
+
+def putch_win(ch):
+    "C-like putch() to stdout, Windows-style."
+
+    msvcrt.putch(ch)
+
+
+# set Unix or Windows implementation for getch/putch
+
+if sys.platform[:3] == "win":
+    import msvcrt
+    getch, putch = getch_win, putch_win
+else:
+    import termios
+    getch, putch = getch_unix, putch_unix
+
+
+# classes (unused)
+
+class ConsoleTypingTutor:
+    def __init__(self): pass
+    def setFile(self, path): pass
+    def printStats(self): pass
+    def outLine(self, line): pass
+    def outText(self, text): pass
+
+
+# functions
+
+def printStats(right, wrong, dt):
+    """Print statistics for counters and elapsed time and typing speed."""
+
+    total = right + wrong
+    r, w, t = right, wrong, total
+    RKC, WKC = RIGHT_KEY_CHAR, WRONG_KEY_CHAR
+    args = (r, 100.*r/t, w, 100.*w/t, t, dt, t/dt*60.)
+    format = "%s:%%d (%%d %%%%), %s:%%d (%%d %%%%)" % (RKC, WKC)
+    format += " - all: %d chars in %4.1f sec -> %d chars/min" 
+    print(format % args)
+
+
+def outLine(line, withCase=True, sound=True, terminal_encoding="utf8", file_encoding="utf8"):
+    """Output one text line and collect data from user while typing it in.
+    Returns (right, wrong, time) tuple."""
+
+    if type(line) == bytes:
+        print(line)
+    elif type(line) == str:
+        print(line) # line.encode(terminal_encoding))
+
+    taboo = (RETURN, BACKSPACE, TAB, ESCAPE)
+    right, wrong = 0, 0
+    started = False
+    for i in range(len(line)):
+        while True:
+            k = getch()
+            if k == CTRL_X:
+                print()
+                sys.exit(1)
+            elif len(k) in (2, 4):
+                break
+            elif len(k) == 1 and k not in taboo:
+                break
+
+        # set t0 only after first accepted key (right or wrong) was hit
+        if started == False:
+            t0 = time.time()
+            started = True
+
+        ch = k
+        if len(k) in (2, 4):
+            decArr = [ord(ki) for ki in k if ord(ki) != 22] # skip ESC(?)
+            ch = "".join(map(chr, decArr)).decode(terminal_encoding)
+
+        if withCase and ch == line[i]:
+            right += 1
+            putch(RIGHT_KEY_CHAR)
+        elif not withCase and ch.upper() == line[i].upper(): 
+            right += 1
+            putch(RIGHT_KEY_CHAR)
+        else:
+            wrong += 1
+            putch(WRONG_KEY_CHAR)
+            if sound:
+                putch(BELL)
+
+    t = time.time() - t0
+    print() 
+
+    return right, wrong, t
+
+
+def outText(text, withCase=True, terminal_encoding="utf8", file_encoding="utf8"):
+    "Output text line by line with some statistics after each line."
+
+    totalRight, totalWrong, totalTime = 0, 0, 0
+    for line in text.split('\n'):
+        line = line.strip()
+        # skipt empty lines
+        if len(line) == 0:
+            continue
+        right, wrong, t = outLine(line, withCase=withCase,
+            terminal_encoding=terminal_encoding, 
+            file_encoding=file_encoding)
+        totalRight += right
+        totalWrong += wrong
+        totalTime += t
+        printStats(totalRight, totalWrong, totalTime)
+        print()
+
+
+# private
+
+def _showShortInfo():
+    "Show short program information."
+
+    name = os.path.basename(sys.argv[0])
+    format = "%s - simple command-line typing tutor"
+    print(format % name)
+    print("Type in the displayed text, or CTRL-C to quit.")
+    print()
+
+
+def _showUsage(msg=''):
+    "Show custom message or correct command-line usage and exit."
+
+    dict = {'name': os.path.basename(sys.argv[0]),
+            'version': __version__}
+    default = """\
+%(name)s v. %(version)s - simple command-line typing tutor
+(c) Dinu C. Gherman, 2005. Licensed under the GPL.
+Usage: %(name)s [options] <file.txt> 
+Options:  -h, --help           this message
+          -t, --test           test mode with predefined text
+          -i                   ignore case
+          --file_encoding      text encoding of input text file
+          --terminal_encoding  text encoding of typing terminal
+Samples:  %(name)s mytext.txt
+          %(name)s -t""" % dict
+
+    print(msg or default)
+    sys.exit(1)
+
+
+def _main():
+    # set defaults for options
+    debug = False
+    help, version = False, False
+    test, ignore = False, True
+    terminal_encoding = "utf8"
+    file_encoding = "utf8"
+
+    # get options and arguments from command-line
+    try:
+        shortOpts = "hvtid"
+        longOpts = ("help", "debug", "test", 
+            "terminal_encoding=", "file_encoding=")
+        opts, args = getopt.getopt(sys.argv[1:], shortOpts, longOpts)
+    except getopt.GetoptError:
+       _showUsage()
+
+    # overwrite default options
+    for k, v in opts:
+        if k in ('-d', '--debug'): 
+            debug = True
+        elif k in ('-h', '--help'): 
+            help = True
+        elif k == '--terminal_encoding': 
+            terminal_encoding = v
+        elif k == '--file_encoding': 
+            file_encoding = v
+        elif k == '-v': 
+            verbose = True
+        elif k in ('-t', '--test'):
+            test = True
+        elif k == '-i':
+            ignore = False
+
+    # perform appropriate action
+    if debug:
+       print(opts, args)
+    if help:
+       _showUsage()
+    if test == True:
+        _showShortInfo()
+        text = """the quick brown fox jumped over the lazy old dog"""
+        outText(text, withCase=ignore, 
+            terminal_encoding=terminal_encoding, 
+            file_encoding=file_encoding)
+        sys.exit(0)
+
+    if not args:
+       _showUsage()
+    else:
+        path = args[0]
+        try:
+            text = open(path).read().decode(file_encoding)
+        except UnicodeDecodeError:
+            msg = "Error: Could not decode file using '%s' codec."
+            print(msg % file_encoding)
+            sys.exit(0)
+        else:
+            _showShortInfo()
+            outText(text, withCase=ignore, 
+                terminal_encoding=terminal_encoding, 
+                file_encoding=file_encoding)
+
+
+if __name__=='__main__':
+    _main()