Skip to content

Instantly share code, notes, and snippets.

@jesstess
Created January 11, 2011 03:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jesstess/773955 to your computer and use it in GitHub Desktop.
Save jesstess/773955 to your computer and use it in GitHub Desktop.
Growl-based flashcards
#!/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