Created
January 11, 2011 03:09
-
-
Save jesstess/773955 to your computer and use it in GitHub Desktop.
Growl-based flashcards
This file contains hidden or 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 | |
from Growl import GrowlNotifier | |
import time | |
import sys | |
import os | |
import optparse | |
""" | |
A Growl-based flashcard system. | |
== Short version == | |
Put flashcard files in a directory. Each entry in the file must be of the form | |
"word,definition". Run the script with | |
flash_cards.py -d /path/to/flashcards/directory | |
and you'll get Growl notifications for a new card every minute. | |
== Long version == | |
You can specify a flashcard directory or file. | |
If you specify a flashcard directory, this script will run through the items in | |
each file in the directory in order. It saves its state between runs, so you'll | |
pick up where you left off when you restart the script. | |
If you specify a flashcard file, this script will run through the items in the | |
file on loop. To save your state, pass the path to a state file (-s, | |
--state-file). | |
Each flashcard entry must be of the form "word,definition". | |
Run this script directly, or under cron. Specify --cron if running under cron, | |
so the script knows to exit after a single flash card. | |
Example cron entry, to flash a card every 5 minutes: | |
*/5 * * * * /usr/local/bin/flash_cards.py -d /var/lib/flashcards --cron | |
Other example invocations: | |
# Run through the entries in the files in the flashcards directory, using the | |
# default timings of a new card every 60 seconds, with 2 seconds between the | |
# sides of a card. Save to the default location -- the file "state" in the | |
# flashcards directory. | |
flash_cards.py -d /var/lib/flashcards/ | |
# Run through the entries in french_adverbs.txt, flashing a card every 10 | |
# seconds, with 1 second between the sides of a card. Save state to | |
# /tmp/french_state. | |
flash_cards.py -f /var/lib/flashcards/french_adverbs.txt -n 10 -i 1 -s /tmp/french_state | |
""" | |
# I don't see a way to control the decay time for notifications. The | |
# minimum time between flash cards to prevent overlap is 6 seconds. | |
MINIMUM_NEW_CARD_DELAY = 6 # in seconds | |
DEFAULT_NEW_CARD_DELAY = 60 # in seconds | |
DEFAULT_INTRA_CARD_DELAY = 2 # in seconds | |
def main(flashcard_file, flashcard_dir, new_card_delay, intra_card_delay, | |
state_file, from_cron): | |
type = "Flash cards" | |
g = GrowlNotifier(notifications=[type]) | |
g.register() | |
# get previous state | |
if not state_file and flashcard_dir: | |
state_file = os.path.join(flashcard_dir, ".state") | |
old_file = "" | |
old_index = 0 | |
old_file_index = 0 | |
if state_file: | |
try: | |
old_file, old_index = file(state_file, "r").readline().split(",") | |
old_index = int(old_index) | |
except (IOError, OSError, ValueError), e: | |
print >>sys.stderr, "Could not open state file, starting cards at beginning." | |
if flashcard_dir: | |
file_list = [ | |
os.path.join(flashcard_dir, elt) for elt in os.listdir(flashcard_dir) | |
] | |
file_list.sort() | |
for i in range(len(file_list)): | |
if old_file == file_list[i]: | |
old_file_index = i | |
else: | |
file_list = [flashcard_file] | |
while 1: | |
for flash_file in file_list[old_file_index:]: | |
if flash_file == ".state": | |
continue | |
entries = file(flash_file, "r").readlines() | |
if old_index > len(entries): | |
# we're past the end of the file | |
old_index = 0 | |
current_index = old_index | |
for elt in entries[old_index:]: | |
try: | |
word, definition = elt.split(",") | |
g.notify(type, word, "") | |
time.sleep(intra_card_delay) | |
g.notify(type, definition, "") | |
except ValueError, e: | |
print >>sys.stderr, e | |
print >>sys.stderr, "Skipping entry %s" % (elt,) | |
# update state | |
current_index += 1 | |
if state_file: | |
file(state_file, "w").write(flash_file + "," + str(current_index)) | |
# If we're running from cron, just show one card, save state, and | |
# exit. | |
if from_cron: | |
sys.exit(0) | |
time.sleep(new_card_delay) | |
# Reset indices go back to the beginning of the first flashcard file. | |
old_file_index = 0 | |
old_index = 0 | |
if __name__ == "__main__": | |
parser = optparse.OptionParser("""Usage: %prog [options]""") | |
parser.add_option("-d", "--flashcard-dir", | |
type="string", | |
action="store", | |
dest="flashcard_dir", | |
help="Path to a directory containing flashcard files.") | |
parser.add_option("-f", "--flashcard-file", | |
type="string", | |
action="store", | |
dest="flashcard_file", | |
help="Path to a flashcard file. Each line is of the form 'word,definition'.") | |
parser.add_option("-n", "--new-card-delay", | |
type="int", | |
action="store", | |
dest="new_card_delay", | |
default=DEFAULT_NEW_CARD_DELAY, | |
help="Time between flashcards, in seconds. Minimum delay is 6 seconds.") | |
parser.add_option("-i", "--intra-card-delay", | |
type="int", | |
action="store", | |
dest="intra_card_delay", | |
default=DEFAULT_INTRA_CARD_DELAY, | |
help="Time between sides of a flashcard, in seconds.") | |
parser.add_option("-s", "--state-file", | |
type="string", | |
action="store", | |
dest="state_file", | |
help="Path to a state file.") | |
parser.add_option("-c", "--cron", | |
action="store_true", | |
dest="from_cron", | |
default=False, | |
help="Running from cron.") | |
(opts, args) = parser.parse_args(sys.argv[1:]) | |
if not opts.flashcard_file and not opts.flashcard_dir: | |
parser.error("You must specify either a flashcard directory or file.") | |
if opts.flashcard_file and opts.flashcard_dir: | |
parser.error("Please specify only one of a flashcard directory or file.") | |
if opts.flashcard_file and not os.path.isfile(opts.flashcard_file): | |
error = "%s is not a file. Please specify a flashcard file, \ | |
or use the --flashcard-dir option." % (opts.flashcard_file,) | |
parser.error(error) | |
if opts.flashcard_dir and not os.path.isdir(opts.flashcard_dir): | |
error = "%s is not a directory. Please specify a flashcard directory, \ | |
or use the --flashcard-file option." % (opts.flashcard_dir,) | |
parser.error(error) | |
try: | |
main(opts.flashcard_file, opts.flashcard_dir, opts.new_card_delay, | |
opts.intra_card_delay, opts.state_file, opts.from_cron) | |
except KeyboardInterrupt: | |
# Might as well suppress the traceback if you Ctl-C. | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment