Created
October 23, 2011 18:54
-
-
Save andres-erbsen/1307709 to your computer and use it in GitHub Desktop.
'Flash cards' implementation, old and possibly buggy in some rarely used code paths, but it works. fileformat is 'answer $ question # comment'
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
from __future__ import division, print_function | |
import os | |
import codecs | |
from sys import argv, hexversion | |
from random import randint | |
from time import sleep | |
__NAME__ = 'Kysija' | |
__VERSION__ = 0.5 | |
configfile = '.kysija.py' | |
helpmessage = u"""Runtime hotkeys: | |
h,help - Shows this text | |
3,õ,a,add - Duplicates last question | |
d,.,',del,delete | |
- Removes last question (but not it's duplicates) | |
ANYNUMBER - Shows question with number. Hint: enable "shownum" first | |
EMPTY - Continues | |
* - Anything else exits | |
Command line arguments: | |
-v, --version - Displays version information | |
-h, --help - You probably already have a good idea, what it does | |
-d, --delimiter=STRING | |
- Manually sets delimiter between question and answer | |
--nosplit - Does not try to guess a delimiter, shows full lines | |
instead of "questions" and "answers" | |
-w, --wait=FORMULA | |
- Sets, how long to wait before displaying answer | |
"0.5+0.1l" - 0.5 seconds plus 0.1 seconds for letter in answer | |
--(no)shownum - (don't) Show number for every question | |
--(no)welcome - Do (not) display a welcome message at startup | |
--(no)forceclear | |
- Try (not) to clear screen even when it seems impossible | |
--(no)confirmexit | |
- Do (not) confirm exit | |
--configfile=FILE | |
- Specify configuration file | |
--mkconfig - Creates or edits configuration file | |
-i, --allow-interrupts | |
- allows to interrupt questions before answer is diplayed. | |
Default Linux, unstable on Windows. | |
--disallow-interrupts | |
- disables this feature on Linux too | |
Tips: | |
* Make the Terminal font BIG | |
* Name all input files .qa and set this program to open these by default | |
""" | |
# Python version specific | |
if hexversion > 0x03000000: | |
raw_input = input | |
# OS specific functions | |
if os.name == "posix": # Unix/Linux/MacOS/BSD/etc | |
clearcommand = 'clear' | |
allow_timed_input = True | |
def inconfigdir(s): | |
return os.path.expanduser(os.path.join('~',s)) | |
from sys import stdin | |
from select import select | |
def timed_input(caption, default=False, timeout=1e308): | |
print(caption,end='') | |
i, o, e = select( [stdin], [], [], timeout ) | |
if i: | |
return stdin.readline().strip() | |
else: | |
return default | |
elif os.name in ("nt", "dos", "ce"): | |
clearcommand = 'CLS' # DOS/Windows | |
allow_timed_input = False | |
def inconfigdir(s): | |
return os.path.expanduser(os.path.join('~',s)).replace(s,os.path.join('Local Settings','Application Data',s)) | |
from threading import Thread | |
import msvcrt | |
class KeyboardThread(Thread): | |
def run(self): | |
self.hasinput = False | |
self.timedout = False | |
self.input = '' | |
while True: | |
if msvcrt.kbhit(): | |
self.hasinput = True | |
chr = msvcrt.getche() | |
if ord(chr) == 13: | |
break | |
elif ord(chr) >= 32: | |
self.input += chr | |
if len(self.input) == 0 and self.timedout: | |
break | |
def timed_input(caption, default=False, timeout=1e308): | |
print(caption,end=''); | |
it = KeyboardThread() | |
it.start() | |
it.join(timeout) | |
it.timedout = True | |
if it.hasinput: | |
print() | |
return it.input | |
else: | |
return default | |
else: | |
clearcommand = False | |
allow_timed_input = False | |
timed_input = False | |
def inconfigdir(s): | |
return s | |
junkstring = '40a2394bdd29dbb2e37264aac2ddd1c1' | |
# Configuration management | |
config = {} | |
configfile = inconfigdir(configfile) | |
try: | |
from configobj import ConfigObj | |
CO = True | |
except: | |
CO = False | |
def unquote(s): | |
if len(s) < 2: | |
return s | |
quotes = ['"',"'",'"""','`'] | |
if s[0] in quotes and s[-1] == s[0]: | |
s = s[1:-1] | |
return unquote(s) | |
return s | |
def sbool(s): | |
if s == 'False': return False | |
return bool(s) | |
def readconfig(configfile): | |
global config, delimiter, waitmod, waitmult, shownum, forceclear, confirmexit, welcome, swap, allow_timed_input | |
if CO and configfile: | |
config = ConfigObj(configfile) | |
delimiter = config.get('delimiter', False) | |
if delimiter == 'False': | |
delimiter = False | |
waitmod, waitmult = float(config.get('waitmod', 0.6)), float(config.get('waitmult',0.02)) # in seconds, equivelent to "--wait=0.07l+0.5" | |
shownum = sbool(config.get('shownum', False)) # tekitab segadust, kuna kustutamisel numbrid nihkuvad | |
forceclear = sbool(config.get('forceclear', False)) | |
confirmexit = sbool(config.get('confirmexit', True)) | |
welcome = sbool(config.get('welcome', True)) | |
swap = sbool(config.get('swap', False)) | |
allow_timed_input = sbool(config.get('allow_timed_input', False)) | |
readconfig(configfile) | |
# exit() that asks for confirmation when needed | |
def doexit(): | |
global confirmexit | |
if confirmexit: | |
if raw_input('Exit(Y/n):') in ['n','no']: return | |
exit() | |
def clear(): | |
global forceclear, clearcommand | |
if clearcommand: | |
os.system(clearcommand) | |
elif forceclear: | |
print('\n' * 100) | |
lastindex = 0 | |
def ask(n=0,cl=True): | |
global shownum, lastindex, tasks, delimiter | |
if cl: | |
clear() | |
if tasks: | |
if not n: n = randint(1,len(tasks)) | |
if shownum: print(str(n)+':',end=' ') | |
lastindex = n-1 | |
a,q = tasks[n-1][0], tasks[n-1][1] | |
if len(q) > 1: | |
print(q) | |
t = len(a)*waitmult+waitmod | |
if timed_input: | |
c = timed_input('',None,t) | |
if c != None: | |
return c | |
else: | |
sleep(t) | |
elif delimiter != junkstring: | |
print(' > ') | |
print(a) | |
else: | |
print("THAT'S ALL!") | |
exit() | |
return False | |
def guessdelimiter(lss): | |
nonprintables = [' ','\t','\n'] | |
counts = {} | |
for line in lss: | |
for char in set(line): | |
if char not in nonprintables: | |
counts[char] = counts.get(char,0) + 1 | |
counttuples = [ (c,counts[c]) for c in counts ] | |
return max(counttuples,key=lambda t: t[-1])[0] | |
# Command line arguments, getting input file name | |
fname = '' | |
if '-v' in argv or '--version' in argv: | |
clear() | |
print(__NAME__, 'v'+str(__VERSION__)) | |
exit() | |
if '-h' in argv or '--help' in argv: | |
clear() | |
print(__NAME__, 'v'+str(__VERSION__)+'\n\n'+helpmessage) | |
exit() | |
for arg in argv[1:]: | |
if arg.startswith('--configfile='): | |
configfile = arg.replace('--configfile=','') | |
configfile = unquote(configfile) | |
configfile = os.path.expanduser(configfile) | |
readconfig(configfile) | |
elif arg.startswith('--delimiter=') or arg.startswith('-d'): | |
delimiter = arg.replace('--delimiter=','').replace('-d','') | |
delimiter = unquote(delimiter) | |
elif arg in ['--nosplit', '--nodelimiter']: | |
delimiter = junkstring | |
elif arg.startswith('--wait=') or arg.startswith('-w'): | |
s = arg.replace('--wait=','').replace('-w','') | |
s = unquote(s) | |
waitmult = waitmod = 0 | |
for g in s.split('+'): | |
if 'l' in g: | |
waitmult += float(''.join([c for c in g if c in '.0123456789'])) | |
else: | |
waitmod += float(''.join([c for c in g if c in '.0123456789'])) | |
elif arg == '--welcome': | |
welcome = True | |
elif arg == '--nowelcome': | |
welcome = False | |
elif arg == '--shownum': | |
shownum = True | |
elif arg == '--noshownum': | |
shownum = False | |
elif arg == '--forceclear': | |
forceclear = True | |
elif arg == '--noforceclear': | |
forceclear = False | |
elif arg == '--confirmexit': | |
confirmexit = True | |
elif arg == '--noconfirmexit': | |
confirmexit = False | |
elif arg == '-s' or arg == '--swap': | |
swap = True | |
elif arg == '--noswap': | |
swap = False | |
elif arg in ['-i', '--allow-interrupts']: | |
allow_timed_input = True | |
elif arg == '--disallow-interrupts': | |
allow_timed_input = False | |
elif arg == '--mkconfig': | |
if CO: | |
config.filename = configfile | |
config['delimiter'] = delimiter | |
config['waitmod'], config['waitmult'] = waitmod, waitmult | |
config['shownum'] = shownum | |
config['forceclear'] = forceclear | |
config['confirmexit'] = confirmexit | |
config['welcome'] = welcome | |
config['swap'] = swap | |
config['allow_timed_input'] = allow_timed_input | |
config.write() | |
exit() | |
else: | |
print("Cannot create configuration file, ConfigObj is missing.") | |
elif not ( fname or arg.startswith('-') ): | |
fname = arg | |
else: | |
print('Unrecognised command line argument "'+arg+'"') | |
exit() | |
if not allow_timed_input: | |
timed_input = False | |
while not fname: | |
fname = unquote(raw_input("Enter path to input file or drag and drop: ").strip()) | |
# Open file | |
try: | |
f = codecs.open(fname,'r',encoding='utf-8') | |
except: | |
print('Error opening file "'+str(fname)+'", please check that it exists and is readable by you', file=sys.stderr) | |
exit() | |
# Prepare for action | |
text = f.read() | |
lines = text.split('\n') | |
# Parse options embedded in qa file. TODO: what options should be allowed to be set this way? | |
while lines[0].startswith('# -*- ') and '-*-' in lines[0][6:]: | |
line = lines[0].replace('# -*- ','') | |
line = line.split('-*-')[0] | |
key, value = line.split(':')[0], ':'.join(line.split(':')[1:]) | |
key = key.strip() | |
value = value.strip() | |
if key in ['delimiter']: # string options | |
vars()[key] = value | |
del lines[0] | |
elif key in ['waitmod','waitmult']: # float options | |
vars()[key] = float(value) | |
del lines[0] | |
elif key in ['swap']: # boolean options | |
vars()[key] = sbool(value) | |
del lines[0] | |
else: | |
print('ERROR: unrecognised option', key ) | |
if not delimiter: | |
delimiter = guessdelimiter(lines) | |
tasks = [] #list of tuples: ('answer','question') | |
for line in lines: | |
if line.startswith('#'): | |
continue # Ignore comment lines | |
line = line.split('#')[0] # Remove inline comments | |
if line.startswith(r'\#'): | |
line = line.replace(r'\#','#') # TODO: is there a better way to allow lines starting with #? | |
t = line.split(delimiter) | |
a = t[0].strip() | |
q = ''.join(t[1:]).strip() | |
if swap == True: | |
a, q = q, a | |
if a: | |
tasks.append((a,q)) | |
# The visible part | |
if welcome: | |
print('[ENTER] to continue, for other options see help (enter "h").') | |
else: | |
ask(False) | |
c = False | |
while True: | |
n = 0 | |
if not c and c != '': | |
c = raw_input() | |
if c == '': pass | |
elif c.lower() in ['h','help']: | |
clear() | |
print(helpmessage) | |
continue | |
elif c.lower() in ["'",'.',',','delete','del','d']: | |
del(tasks[lastindex]) # deletes last question, but not its duplicates | |
elif c.lower() in ['3',u'õ','õ',b'õ','add','z']: | |
tasks.append(tasks[lastindex]) # Makes last question occur more (duplicates) | |
elif c.isdigit(): | |
n = (int(c)-1)%len(tasks) + 1 | |
else: doexit() | |
c = ask(n) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment