diff options
Diffstat (limited to 'generate_karma_lesson.py')
-rwxr-xr-x | generate_karma_lesson.py | 692 |
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 + |