Skip to content

Instantly share code, notes, and snippets.

@andres-erbsen
Created October 23, 2011 18:54
Show Gist options
  • Save andres-erbsen/1307709 to your computer and use it in GitHub Desktop.
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'
#!/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