Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/generate_karma_lesson.py
diff options
context:
space:
mode:
Diffstat (limited to 'generate_karma_lesson.py')
-rwxr-xr-xgenerate_karma_lesson.py692
1 files changed, 692 insertions, 0 deletions
diff --git a/generate_karma_lesson.py b/generate_karma_lesson.py
new file mode 100755
index 0000000..e0b532e
--- /dev/null
+++ b/generate_karma_lesson.py
@@ -0,0 +1,692 @@
+#! /usr/bin/env python2.6
+# -*- coding: utf-8 -*-
+
+from html import HtmlDocument, HtmlElement
+import mo2js
+import codecs
+import os
+import shutil
+import string
+import sys
+import time
+import fnmatch
+from optparse import OptionParser
+from path import path
+
+class KarmaFramework():
+ def __init__(self, root_dir):
+ self.root_dir = root_dir
+ self.java_script_files = [
+ self._karma_file('js/external/jquery-1.4.2.js', 'jquery'),
+ self._karma_file('js/external/jquery-ui-1.8.2.js', 'jquery-ui'),
+ self._karma_file('js/external/jquery.ui.core.js', 'ui.core'),
+ self._karma_file('js/external/jquery.ui.mouse.js', 'ui.mouse'),
+ self._karma_file('js/external/jquery.ui.widget.js', 'ui.widget'),
+ self._karma_file('js/external/jquery.ui.position.js', 'ui.position'),
+ self._karma_file('js/external/jquery.ui.draggable.js', 'ui.draggable'),
+ self._karma_file('js/external/jquery.ui.droppable.js', 'ui.droppable'),
+ self._karma_file('js/jquery.watermarkinput.js', 'jquery.watermarkinput'),
+ self._karma_file('js/ui.scoreboard.js', 'ui.scoreboard'),
+ self._karma_file('js/jquery.svg.js', 'jquery.svg'),
+ self._karma_file('js/karma.js', 'karma'),
+ self._karma_file('js/global.js', 'global'),
+ self._karma_file('js/common.js', 'common'),
+ self._karma_file('js/jquery.clickable.js', 'jquery.clickable'),
+ self._karma_file('js/multiple-choice.js', 'multiple-choice'),
+ self._karma_file('js/clock.js', 'clock'),
+ self._karma_file('js/jquery.i18n.js', 'i18n'),
+ self._karma_file('js/jquery.strings.js', 'jquery.strings'),
+ self._karma_file('js/jquery.keyfilter.js', 'jquery.keyfilter'),
+ self._karma_file('js/kStart.js', 'kstart'),
+ self._karma_file('js/config.js', 'config'),
+ self._karma_file('js/base.js','base')
+ ]
+ self.css_files = [
+ self._karma_file('css/global.css', 'global'),
+ self._karma_file('css/ui.scoreboard.css', 'ui.scoreboard'),
+ self._karma_file('css/kStart.css', 'kstart')
+ ]
+ self.audio_files = [
+ self._karma_file('audio/en_correct.ogg', 'correct'),
+ self._karma_file('audio/en_incorrect.ogg', 'incorrect'),
+ self._karma_file('audio/ne_correct.ogg', 'ne_correct'),
+ self._karma_file('audio/ne_incorrect.ogg', 'ne_incorrect'),
+ self._karma_file('audio/byebye.ogg', 'byebye'),
+ self._karma_file('audio/trigger.ogg', 'trigger')
+ ]
+ self.image_files = [
+ self._karma_file('image/title_block_lt.png', 'title_block_lt'),
+ self._karma_file('image/title_block_rt.png', 'title_block_rt'),
+ self._karma_file('image/favicon.ico', 'favicon')
+ ]
+
+ def _karma_file(self, path, name, **kw):
+ kw['karma_root'] = self.root_dir
+ return KarmaFile(path, name, **kw)
+
+ def _find_file(self, name, files):
+ for f in files:
+ if f.name() == name:
+ return f
+ return None
+
+ def java_script(self, name):
+ return self._find_file(name, self.java_script_files)
+
+ def css(self, name):
+ return self._find_file(name, self.css_files)
+
+ def audio(self, name):
+ return self._find_file(name, self.audio_files)
+
+ def image(self, name):
+ return self._find_file(name, self.image_files)
+
+
+argv0 = sys.argv[0]
+now = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime())
+warning_text_lines = ['This file was generated by %s on %s.' % (argv0, now),
+ 'Do not edit.',
+ 'cwd: %s' % os.getcwd(),
+ 'command: %s' % ' '.join(sys.argv)]
+
+
+theLesson = None
+
+include_stack = []
+script_root = os.path.abspath(os.path.dirname(argv0))
+
+
+class File():
+ _name = None
+ src = ''
+ lesson_deploy = True
+ create_file = False
+ data = ''
+
+ def to_string(self):
+ print 'name:', self._name
+ print 'src:', self.src
+ print 'lesson_deploy:', self.lesson_deploy
+ print 'create_file:', self.create_file
+ print 'data:', self.data
+ print
+
+ def __init__(self, pth, name=None, **kw):
+ self._name = name
+ self.src = pth
+
+ if 'karma' in kw and kw['karma']:
+ self.lesson_deploy = False
+ if 'generated' in kw and kw['generated']:
+ self.create_file = True
+
+ if not self.create_file:
+ # find the existing file
+ test_files = []
+ if self.lesson_deploy:
+ test_files.append(os.path.join(theLesson.src_directory, pth))
+ if len(include_stack) > 0:
+ test_files.append(os.path.join(os.path.dirname(include_stack[-1]), pth))
+ if 'karma_root' in kw:
+ test_files.append(os.path.join(kw['karma_root'], pth))
+
+ for f in test_files:
+ abs_path = os.path.abspath(f)
+ if os.path.isfile(abs_path):
+ self.src = abs_path
+ else:
+ self.src = self._absolute_path()
+
+ def _deploy_folder(self):
+ return os.path.abspath(path(theLesson.directory).parent)
+
+ def name(self):
+ return self._name
+
+ # only applicable for assets
+ def preload(self):
+ return self._name != None
+
+ def _src_path(self):
+ return self.src
+
+ def relative_path(self, start=None, **kw):
+ if start == None or start == '':
+ # default relative is to lesson output
+ start = theLesson.directory
+ elif start == 'deploy':
+ start = self._deploy_folder()
+
+ #rel_path = os.path.relpath(self._absolute_path(), start)
+ pth = str(self._absolute_path())
+ pos = pth.find('/karma/')
+ if pos > -1:
+ rel_path = path('../../../..' + pth[pos:])
+ else:
+ rel_path = path(pth).name
+ if 'web' in kw and kw['web']:
+ rel_path = string.replace(rel_path, '\\', '/')
+
+ return rel_path
+
+ def _absolute_path(self):
+ if self.lesson_deploy:
+ return os.path.join(self._deploy_folder(), self._basename())
+ else:
+ return self.src
+
+ def _basename(self):
+ return os.path.basename(self.src)
+
+ def make_available(self):
+ if self.create_file:
+ f = open(self._absolute_path(), 'w')
+ print >>f, self.data
+ f.close()
+ elif self.lesson_deploy:
+ check_file_exists(self._src_path())
+ shutil.copy(self._src_path(), self._absolute_path())
+
+ # only applicable for generated files
+ def write(self, x):
+ self.data = self.data + x
+
+
+class KarmaFile(File):
+ def __init__(self, path, name=None, **kw):
+ kw['karma'] = True
+ File.__init__(self, path, name, **kw)
+
+
+java_script_dependencies = [
+ ('effects.core', 'effects.blind'),
+ ('effects.core', 'effects.bounce'),
+ ('effects.core', 'effects.clip'),
+ ('effects.core', 'effects.drop'),
+ ('effects.core', 'effects.explode'),
+ ('effects.core', 'effects.fold'),
+ ('effects.core', 'effects.highlight'),
+ ('effects.core', 'effects.pulsate'),
+ ('effects.core', 'effects.scale'),
+ ('effects.core', 'effects.shake'),
+ ('effects.core', 'effects.slide'),
+ ('effects.core', 'effects.transfer'),
+ ('ui.core', 'ui.accordion'),
+ ('ui.widget', 'ui.accordion'),
+ ('ui.core', 'ui.autocomplete'),
+ ('ui.widget', 'ui.autocomplete'),
+ ('ui.position', 'ui.autocomplete'),
+ ('ui.core', 'ui.button'),
+ ('ui.widget', 'ui.button'),
+ ('ui.core', 'ui.datepicker'),
+ ('ui.core', 'ui.dialog'),
+ ('ui.widget', 'ui.dialog'),
+ ('ui.button', 'ui.dialog'),
+ ('ui.draggable', 'ui.dialog'),
+ ('ui.mouse', 'ui.dialog'),
+ ('ui.position', 'ui.dialog'),
+ ('ui.resizable', 'ui.dialog'),
+ ('ui.core', 'ui.draggable'),
+ ('ui.mouse', 'ui.draggable'),
+ ('ui.widget', 'ui.draggable'),
+ ('ui.core', 'ui.droppable'),
+ ('ui.widget', 'ui.droppable'),
+ ('ui.mouse', 'ui.droppable'),
+ ('ui.draggable', 'ui.droppable'),
+ ('ui.widget', 'ui.mouse'),
+ ('ui.core', 'ui.progressbar'),
+ ('ui.widget', 'ui.progressbar'),
+ ('ui.core', 'ui.resizable'),
+ ('ui.mouse', 'ui.resizable'),
+ ('ui.widget', 'ui.resizable'),
+ ('ui.core', 'ui.selectable'),
+ ('ui.mouse', 'ui.selectable'),
+ ('ui.widget', 'ui.selectable'),
+ ('ui.core', 'ui.slider'),
+ ('ui.mouse', 'ui.slider'),
+ ('ui.widget', 'ui.slider'),
+ ('ui.core', 'ui.sortable'),
+ ('ui.mouse', 'ui.sortable'),
+ ('ui.widget', 'ui.sortable'),
+ ('ui.core', 'ui.tabs'),
+ ('ui.widget', 'ui.tabs'),
+ # old stuff
+ ('jquery', 'jquery-ui'),
+ ('jquery', 'jquery.watermarkinput'),
+ ('jquery', 'jquery.clickable'),
+ ('ui.core', 'ui.scoreboard'),
+ ('jquery-ui', 'ui.scoreboard'),
+ ('jquery', 'jquery.svg'),
+ ('karma', 'common'),
+ ('common', 'multiple-choice'),
+ ('common', 'clock'),
+ ('common', 'base'),
+ ('jquery', 'clock'),
+ ('jquery', 'i18n')
+ ]
+
+#TBD: factor this out in a separate file, so it is easy to provide
+# your own header/footer
+#TBD: make header/footer customizable
+def generate_header(karma, dir, body, title):
+ header = body.div(id='header')
+
+ header.div(id='topbtn_left').div(id='linkBackLesson',
+ title='Back',
+ className='linkBack')
+
+ lesson_title = header.div(id='lesson_title')
+ lesson_title.img(src=karma.image('title_block_lt').relative_path(dir, web=True),
+ width=33, height=75, align='absmiddle')
+ lesson_title.text(title)
+ lesson_title.img(src=karma.image('title_block_rt').relative_path(dir, web=True),
+ width=33, height=75, align='absmiddle')
+
+
+ header.div(className='topbtn_right').div(title='Help', id='linkHelp')
+
+ header.div(className='topbtn_right').div(title=u'साझा शिक्षा ई-पाटी द्वारा निर्मित',
+ id='linkOle')
+
+
+def generate_footer(body):
+ footer = body.div(id='footer')
+
+ config = theLesson.footer_configuration
+
+ if config['link_next']:
+ footer.div(title='Next', id='linkNextLesson', className='linkNext')
+ if config['link_previous']:
+ footer.div(title='Previous', id='linkPrevLesson', className='linkBack')
+ if config['scoreboard']:
+ footer.div(id='score_box', display='none')
+
+ footer.div(className='botbtn_right').div(title='Play Again', id='linkPlayAgain')
+
+ if config['link_check_answer']:
+ footer.div(className='botbtn_right').div(title='Check Answer', id='linkCheck')
+
+
+def topological_sort(nodes, dependencies, key):
+ """Sort nodes topologically according to dependencies.
+ A dependency is a tuple (key(earlier_node), key(later_node)),
+ meaning that earlier_node should come before later_node in the
+ result."""
+ from collections import deque
+ successors = {}
+ predecessor_count = {}
+ node_map = {}
+ for node in nodes:
+ k = key(node)
+ node_map[k] = node
+ successors[k] = []
+ predecessor_count[k] = 0
+ for (dep0, dep1) in dependencies:
+ if dep0 in node_map and dep1 in node_map:
+ successors[dep0].append(dep1)
+ predecessor_count[dep1] = predecessor_count[dep1] + 1
+ next = deque()
+ for k,v in successors.items():
+ if predecessor_count[k] == 0:
+ next.append(k)
+ result = []
+ while len(next) != 0:
+ k = next.popleft()
+ result.append(node_map[k])
+ for successor in successors[k]:
+ predecessor_count[successor] = predecessor_count[successor] - 1
+ if predecessor_count[successor] == 0:
+ next.append(successor)
+ if len(result) != len(nodes):
+ print 'Error: dependency loop.'
+ sys.exit(1)
+ return result
+
+
+def include_dependencies(karma, files):
+ result = []
+ visited = set()
+ deps = {}
+ for dep in java_script_dependencies:
+ deps[dep[1]] = deps.setdefault(dep[1], []) + [dep[0]]
+ js_files = {}
+ for f in karma.java_script_files:
+ js_files[f.name()] = f
+ def add_dependencies(list):
+ for x in list:
+ if x not in visited:
+ add_dependencies([js_files[name] for name
+ in deps.setdefault(x.name(), [])])
+ result.append(x)
+ visited.add(x)
+ add_dependencies(files)
+ return result
+
+def sort_java_script_files(files):
+ karma_files = filter(lambda x: isinstance(x, KarmaFile), files)
+ other_files = filter(lambda x: not isinstance(x, KarmaFile), files)
+ result = topological_sort(karma_files,
+ java_script_dependencies,
+ lambda x: x.name()) + other_files
+ return result
+
+
+def createDiv(id):
+ return HtmlElement('body', 'div', True).attr(id=id)
+
+class Lesson():
+ def __init__(self, src_directory):
+ self.src_directory = src_directory
+ self.parent_directory = ''
+ self.directory = ''
+ self.title = ''
+ self.lesson_title = ''
+ self._grade = None;
+ self._subject = '';
+ self._week = None
+ self.summary = '';
+ self.java_script_files = []
+ self.css_files = []
+ self.image_files = []
+ self.audio_files = []
+ self.divs = [createDiv('content')]
+ self.footer_configuration = dict(link_previous=True,
+ link_next=True,
+ scoreboard=False,
+ link_check_answer=False,
+ audio_buttons=False)
+
+ def grade(self):
+ return self._grade
+
+ def subject(self):
+ return self._subject
+
+ def week(self):
+ return self._week
+
+ def copy_files(self):
+ def create_dir(d):
+ if not os.path.exists(d):
+ os.makedirs(d)
+ create_dir(self.parent_directory)
+ os.chdir(self.parent_directory)
+# map(create_dir, ['css', 'js', 'js/locale', 'assets/image', 'assets/audio', 'assets/video'])
+ for f in self.java_script_files + self.css_files:
+ f.make_available()
+ for f in self.image_files + self.audio_files:
+ f[1].make_available()
+
+ def copy_required(f):
+ src = os.path.join(self.src_directory, f)
+ if (os.path.exists(src)):
+ shutil.copy(src, self.directory)
+ else:
+ print 'Warning: missing ' + src
+
+ # if a screenshot.jpg exists in the source, copy it to the dest
+ screenshot_img = os.path.join(self.src_directory, 'screenshot.jpg')
+ if (os.path.exists(screenshot_img)):
+ shutil.copy(screenshot_img, os.path.join(self.directory, 'screenshot.jpg'))
+
+ self.compile_translations()
+
+ def name(self):
+ return self.deploy_name()
+
+ def deploy_name(self):
+ return os.path.basename(self.directory)
+
+ def generate(self):
+ print 'writing lesson to ' + self.deploy_name()
+ self.copy_files()
+ self.print_html_on(codecs.open('index.html', 'w', 'UTF-8'))
+ #self.print_start_html_on(codecs.open('start.html', 'w', 'UTF-8'))
+ #self.print_kdoc_html_on(codecs.open('kDoc.html', 'w', 'UTF-8'))
+ self.print_karma_js_on(open('lesson-karma.js', 'w'))
+
+ def compile_translations(self):
+ # compile translation JS files from MO files
+ for srcfile in os.listdir(self.src_directory):
+ if fnmatch.fnmatch(srcfile, '*.mo'):
+ lang = os.path.splitext(srcfile)[0]
+ srcpath = os.path.join(self.src_directory, srcfile)
+ targpath = os.path.join(self.directory, lang +'.js')
+ json_translations = mo2js.gettext_json(open(srcpath, 'r'), True)
+
+ f = codecs.open(targpath, encoding='utf-8', mode='w+')
+ f.write('$.i18n.storeLocaleStrings("%s",\n' % lang);
+ f.write(json_translations)
+ f.write(');\n');
+ f.write('$.i18n.setLocale("%s");\n' % lang);
+
+ def set_directory(self, dir):
+ self.directory = os.path.abspath(os.path.join(self.parent_directory, dir))
+
+ def print_html_on(self, stream):
+ doc = HtmlDocument()
+ html = doc.html()
+ head = html.head()
+ head.title().text(self.title)
+ head.meta(content='text/html, charset=utf-8', httpEquiv='Content-Type')
+ head.link(type='image/ico',
+ rel='icon',
+ href=self.karma.image('favicon').relative_path(None, web=True))
+ for file in self.css_files:
+ head.link(type='text/css',
+ rel='stylesheet',
+ href=file.relative_path(None, web=True))
+ all_java_script_files = include_dependencies(self.karma,
+ self.java_script_files)
+ for file in sort_java_script_files(all_java_script_files):
+ head.script(type='text/javascript',
+ src=file.relative_path(None, web=True))
+ head.script(type='text/javascript',
+ src='../../../../subjects.js')
+ body = html.body()
+ header = body.div(id='header')
+ body.children.extend(self.divs)
+ footer = body.div(id='footer')
+ doc.print_on(stream)
+
+ def print_karma_js_on(self, stream):
+ print >>stream, '/*'
+ for l in warning_text_lines:
+ print >>stream, ' *', l
+ print >>stream, ' */'
+ def format_image(img):
+ return "{name:'%s', file:'%s'}" % (img[0], img[1].relative_path('deploy', web=True))
+ def format_audio(a):
+ return "{name:'%s', file:'%s'}" % (a[0], a[1].relative_path('deploy', web=True))
+ def format_assets(name, assets, format_asset, indentation):
+ prefix = '%s: [' % name
+ sep = ',\n' + (len(prefix) + indentation) * ' '
+ postfix = ']'
+ to_preload = filter(lambda asset: asset[1].preload(), assets)
+ return prefix + sep.join(map(format_asset, to_preload)) + postfix
+ print >>stream, 'function lesson_karma() {'
+ return_karma = ' return Karma({'
+ indentation = len(return_karma)
+ print >>stream, return_karma + (',\n' + indentation * ' ').join(
+ [format_assets('image',
+ self.image_files,
+ format_image,
+ indentation),
+ format_assets('audio',
+ self.audio_files,
+ format_audio,
+ indentation)]) + '});'
+ print >>stream, '}'
+
+def lesson(grade, subject, title, week, browser_title=None, lesson_title=None, locale=None, summary=''):
+ def camelcase(str):
+ words = str.replace("'", '').split()
+ return ''.join([words[0].lower()] + [x.capitalize() for x in words[1:]])
+
+ #dirname = '%s_%s_%s_%s_K' % (grade, subject, camelcase(title), week);
+ title = path(theLesson.parent_directory).namebase
+ dirname = title
+ theLesson.set_directory(path(theLesson.parent_directory).namebase)
+ theLesson.start_title = title
+ theLesson.title = title
+ theLesson.lesson_title = lesson_title or title
+ theLesson._grade = grade
+ theLesson._subject = subject
+ theLesson._week = week
+ theLesson.summary = summary
+ java_script('jquery')
+ java_script('karma')
+ java_script('common')
+ java_script('i18n')
+ java_script('base')
+ # include the lesson.js if it exists
+ lesson_js = frob_path('lesson.js')
+ if os.path.exists(lesson_js):
+ java_script('lesson.js')
+ #add_help()
+ # include the locale strings too
+
+ if locale != None:
+ theLesson.java_script_files.append(File('jquery.i18n.'+ locale +'.js', type='js', karma=True))
+
+ locale_mo = frob_path(locale + '.mo')
+ if os.path.exists(locale_mo):
+ targpath = os.path.join(theLesson.directory, locale +'.js')
+ theLesson.java_script_files.append(File(targpath, None, type='js', karma=True))
+
+
+def java_script(name, **kw):
+ result = theLesson.karma.java_script(name)
+ if not result:
+ result = File(name, name, **kw)
+ if name in [f.name() for f in theLesson.java_script_files]:
+ print 'Warning: the java_script file \'' + name + '\' is included twice.'
+ else:
+ theLesson.java_script_files.append(result)
+ return result
+
+
+def css(name):
+ result = theLesson.karma.css(name)
+ if not result:
+ result = File(name, name)
+ theLesson.css_files.append(result)
+ return result
+
+
+def image(file, name=None):
+ result = None
+ if name == None:
+ name = file
+ result = theLesson.karma.image(name)
+ if not result:
+ result = File(file, name)
+ theLesson.image_files.append([name, result])
+ return result
+
+
+def audio(file, name=None):
+ result = None
+ if name == None:
+ name = file
+ result = theLesson.karma.audio(name)
+ if not result:
+ result = File(file, name)
+ theLesson.audio_files.append([name, result])
+ return result
+
+
+def div(**info):
+ if 'id' in info and info['id'] == 'content':
+ print 'Warning: div(id=\'content\') no longer needed (it\'s added automatically).'
+ return None
+ result = createDiv(info['id'])
+ theLesson.divs.append(result)
+ return result
+
+
+def footer_configuration(**kw):
+ global theLesson
+ config = theLesson.footer_configuration
+ for k,v in kw.items():
+ if not k in config:
+ print 'Error: unsupported footer configuration option: ' + k + '.'
+ print 'Possible options:', ', '.join(config.keys())
+ sys.exit(1)
+ config[k] = v
+ if config['scoreboard']:
+ css('ui.scoreboard')
+ java_script('ui.scoreboard')
+
+
+def frob_path(path):
+ if not os.path.isabs(path):
+ return os.path.normpath(os.path.join(os.path.dirname(include_stack[-1]),
+ path))
+ else:
+ return os.path.abspath(path)
+
+
+def include(pth):
+ pth = frob_path(pth)
+ include_stack.append(pth)
+ check_file_exists(pth)
+ execfile(pth, globals())
+ include_stack.pop()
+
+
+def add_help():
+ # add html help content if it exists, otherwise the help image
+ help_html = frob_path('help.html')
+ help_img = frob_path('help.png')
+ if (os.path.exists(help_html)):
+ f = codecs.open(help_html, 'r', 'UTF-8')
+ div(id='help').div(id='helpText').innerhtml(f.read())
+ elif (os.path.exists(help_img)):
+ img = image(help_img, 'help')
+ src = img.relative_path(None, web=True)
+ div(id='help').img(src=src)
+ else:
+ print 'Warning: the file ' + str(help_path) + ' doesn\'t exist.'
+
+
+def check_file_exists(path):
+ if not os.path.isfile(path):
+ print 'Error: the file ' + path + ' doesn\'t exist.'
+ sys.exit(1)
+
+
+def find_all_description_files():
+ result = []
+ lesson_folder = os.path.join(script_root, 'lessons')
+ for root, dirs, files in os.walk(lesson_folder):
+ if 'description.py' in files:
+ result.append(os.path.abspath(os.path.join(script_root, root, 'description.py')))
+ return result
+
+
+def constantly(x):
+ return lambda y: x
+
+
+def process_description(karma, description, output_dir, lesson_filter=constantly(True)):
+ os.chdir(script_root)
+ description = os.path.abspath(description)
+
+ global theLesson
+ theLesson = Lesson(os.path.abspath(os.path.dirname(description)))
+ theLesson.karma =karma
+ theLesson.parent_directory = os.path.abspath(output_dir)
+ theLesson.java_script_files.append(File('lesson-karma.js', None, generated=True))
+ include_stack.append(description)
+ check_file_exists(description)
+ execfile(description, globals())
+ include_stack.pop()
+
+ if lesson_filter(theLesson):
+ theLesson.generate()
+ return theLesson
+ else:
+ return None
+