def join_children(self, path, lang1, c1_list, lang2, c2_list): c_list = [] for i,c1 in enumerate( self.require_identical_list_len(path, lang1, c1_list, lang2, c2_list) ): c2 = c2_list[i] c_path = path + [str(i + 1)] c = {} self.require_identical_dict_keys(c_path, lang1, c1, lang2, c2, ACCEPTED_CHILDREN_DEFAULT_KEYS) key = join_keys(lang1, c1.get('key', ''), lang2, c2.get('key', '')) for k,v in c1.items(): if k == 'key': c[k] = key elif k in ('name', 'title', 'static_content'): c[k] = join_values(lang1, v, lang2, c2.get(k, v)) elif k == 'config': e1 = yaml_writer.read(yaml_writer.file_path(self.app.env, v)) e2 = yaml_writer.read(yaml_writer.file_path(self.app.env, c2.get(k, v))) yaml_writer.write( yaml_writer.file_path(self.app.env, key), self.join_exercises(key, lang1, e1, lang2, e2) ) c[k] = key + '.yaml' elif k == 'children': c[k] = self.join_children(c_path, lang1, v, lang2, c2.get(k, [])) elif deep_equals(v, c2.get(k, v)): c[k] = v else: self.raise_unequal(c_path, lang2, k) c_list.append(c) return c_list
def run(self): env = self.state.document.settings.env if not 'config' in self.options: raise SphinxError('Config option is required') import os path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) item_list = yaml_writer.read(path) itemnodes = [] for item in item_list: title, info, img = [item.get(u"title", u""), item.get(u"info", u""), item.get(u"image_url", u"")] _, node, data = self.create_question(title_text=self.arguments[0].replace(u"$title", title), points=False) more = u"" if img: e = aplus_nodes.html(u"p", {u"class": u"indent"}) e.append(aplus_nodes.html(u"img", {u"src":img, u"alt": title, u"style": u"max-height:100px;"})) node.append(e) more += str(e) if info: e = aplus_nodes.html(u"p", {u"class": u"indent"}) e.append(nodes.Text(info)) node.append(e) more += str(e) data[u'options'] = self.generate_options(env, node) data[u'more'] = more itemnodes.append(node) return itemnodes
def run(self): env = self.state.document.settings.env if not 'config' in self.options: raise SphinxError('Config option is required') import os path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) item_list = yaml_writer.read(path) itemnodes = [] for item in item_list: title, info, img = [item.get(u"title", u""), item.get(u"info", u""), item.get(u"image_url", u"")] _, node, data = self.create_question(title_text=self.arguments[0].replace(u"$title", title), points=False) more = u"" if img: e = aplus_nodes.html(u"p", {u"class": u"indent"}) e.append(aplus_nodes.html(u"img", {u"src":img, u"alt": title, u"style": u"max-height:100px;"})) node.append(e) more += str(e) if info: e = aplus_nodes.html(u"p", {u"class": u"indent"}) e.append(nodes.Text(info)) node.append(e) more += str(e) data[u'options'] = self.generate_options(env, node) data[u'more'] = more itemnodes.append(node) return itemnodes
def append_manual_content(app, index): def recursive_merge(config, append): if type(append) == dict: for key, val in append.items(): if not key in config: config[key] = val else: recursive_merge(config[key], append[key]) elif type(append) == list: for entry in append: add = True if 'key' in entry: for old in config: if 'key' in old and old['key'] == entry['key']: recursive_merge(old, entry) add = False if add: config.append(entry) for path in app.config.append_content: recursive_merge(index, yaml_writer.read(path))
def parse_chapter(docname, doc, parent): for config_file in [ e.yaml_write for e in doc.traverse(aplus_nodes.html) if e.has_yaml(u'exercise') ]: config = yaml_writer.read(config_file) if config.get(u'_external', False): exercise = config.copy() del exercise[u'_external'] else: exercise = { u'key': config[u'key'], u'config': config[u'key'] + u'.yaml', u'max_submissions': config.get(u'max_submissions', 0), u'max_points': config.get(u'max_points', 0), u'difficulty': config.get(u'difficulty', ''), u'points_to_pass': config.get(u'points_to_pass', 0), u'category': config[u'category'], u'min_group_size': config.get(u'min_group_size', 1), u'max_group_size': config.get(u'max_group_size', 1), u'confirm_the_level': config.get(u'confirm_the_level', False), u'allow_assistant_grading': config.get(u'allow_assistant_grading', False), } exercise.update({ u'status': u'unlisted', }) if u'scale_points' in config: exercise[u'max_points'] = config.pop(u'scale_points') parent.append(exercise) if not config[u'category'] in category_keys: category_keys.append(config[u'category']) category = u'chapter' for name, hidden, child in traverse_tocs(app, doc): meta = first_meta(child) status = u'hidden' if 'hidden' in meta else ( u'unlisted' if hidden else u'ready') chapter = { u'key': name.split(u'/')[-1], #name.replace('/', '_'), u'status': status, u'name': first_title(child), u'static_content': name + u'.html', u'category': category, u'use_wide_column': app.config.use_wide_column, u'children': [], } if meta: audience = meta.get('audience') if audience: chapter[u'audience'] = yaml_writer.ensure_unicode(audience) if category in override: chapter.update(override[category]) parent.append(chapter) if not u'chapter' in category_keys: category_keys.append(u'chapter') parse_chapter(name, child, chapter[u'children'])
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', u'data-aplus-active-element': u'out', u'data-inputs': u''+ self.options.get('inputs', ''), } if 'inputs' not in self.options: raise self.warning("The input list for output '{:s}' is empty.".format(key)) if 'type' in self.options: args['data-type'] = self.options['type'] else: args['data-type'] = 'text' if 'scale-size' in self.options: args['data-scale'] = '' if 'title' in self.options: args['data-title'] = self.options['title'] if 'width' in self.options: args['style'] = 'width:'+ self.options['width'] + ';' if 'height' in self.options: if 'style' not in args: args['style'] = 'height:'+ self.options['height'] + ';' else: args['style'] = args['style'] + 'height:'+ self.options['height'] + ';' if 'clear' in self.options: args['style'] = args['style'] + 'clear:'+ self.options['clear'] + ';' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = { u'_external': True } if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) config_title = None config_title = self.options.get('title', config_title) category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format( key_title=key_title, config_title=config_title ), u'category': u'active elements', u'max_submissions': self.options.get('submissions', data.get('max_submissions', env.config.ae_default_submissions)), }) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) node.write_yaml(env, name, data, 'exercise') return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = "{}_{}".format(env.docname.replace('/', '_'), key) override = env.config.override classes = ['exercise'] if 'class' in self.options: classes.extend(self.options['class']) if difficulty: classes.append('difficulty-' + difficulty) # Add document nodes. args = { 'class': ' '.join(classes), 'data-aplus-exercise': 'yes', } if 'quiz' in self.options: args['data-aplus-quiz'] = 'yes' if 'ajax' in self.options: args['data-aplus-ajax'] = 'yes' node = aplus_nodes.html('div', args) key_title = "{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format( self.options['config'])) data = yaml_writer.read(path) config_title = data.get('title', '') else: data = {'_external': True} if 'url' in self.options: data['url'] = self.options['url'] if 'lti' in self.options: data.update({ 'lti': self.options['lti'], 'lti_context_id': self.options.get('lti_context_id', ''), 'lti_resource_link_id': self.options.get('lti_resource_link_id', ''), }) if 'lti_aplus_get_and_post' in self.options: data.update({'lti_aplus_get_and_post': True}) if 'lti_open_in_iframe' in self.options: data.update({'lti_open_in_iframe': True}) config_title = '' config_title = self.options.get('title', config_title) if "radar_tokenizer" in self.options or "radar_minimum_match_tokens" in self.options: data['radar_info'] = { 'tokenizer': self.options.get("radar_tokenizer"), 'minimum_match_tokens': self.options.get("radar_minimum_match_tokens"), } category = 'submit' data.update({ 'key': name, 'category': 'submit', 'scale_points': points, 'difficulty': difficulty or '', 'max_submissions': self.options.get( 'submissions', data.get('max_submissions', env.config.program_default_submissions)), 'min_group_size': data.get('min_group_size', env.config.default_min_group_size), 'max_group_size': data.get('max_group_size', env.config.default_max_group_size), 'points_to_pass': self.options.get('points-to-pass', data.get('points_to_pass', 0)), # The RST source file path is needed for fixing relative URLs # in the exercise description. # Replace the Windows path separator backslash \ with the Unix forward slash /. '_rst_srcpath': env.doc2path(env.docname, None).replace('\\', '/'), }) self.set_assistant_permissions(data) if data.get('title|i18n'): # Exercise config.yaml defines title|i18n for multiple languages. # Do not write the field "title" to data in order to avoid conflicts. if config_title: # Overwrite the title for one language since the RST directive # has defined the title option (or alternatively, the yaml file # has "title" in addition to "title|i18n", but that does not make sense). # env.config.language may be incorrect if the language can not be detected. data['title|i18n'][ env.config.language] = env.config.submit_title.format( key_title=key_title, config_title=config_title) else: formatted_title = env.config.submit_title.format( key_title=key_title, config_title=config_title) # If no title has been defined, use key_title as the default. data['title'] = formatted_title if formatted_title else key_title if self.content: self.assert_has_content() # Sphinx can not compile the nested RST into HTML at this stage, hence # the HTML instructions defined in this directive body are added to # the exercise YAML file only at the end of the build. Sphinx calls # the visit functions of the nodes in the last writing phase. # The instructions are added to the YAML file in the depart_html # function in aplus_nodes.py. exercise_description = aplus_nodes.html('div', {}) exercise_description.store_html('exercise_description') nested_parse_with_titles(self.state, self.content, exercise_description) node.append(exercise_description) data['instructions'] = ('#!html', 'exercise_description') else: # The placeholder text is only used in the built HTML # (not in the YAML configurations). paragraph = aplus_nodes.html('p', {}) paragraph.append( nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) data.setdefault('status', self.options.get('status', 'unlisted')) source, line = self.state_machine.get_source_and_line(self.lineno) if 'reveal-submission-feedback' in self.options: data['reveal_submission_feedback'] = parse_reveal_rule( self.options['reveal-submission-feedback'], source, line, 'reveal-submission-feedback', ) if 'reveal-model-solutions' in self.options: data['reveal_model_solutions'] = parse_reveal_rule( self.options['reveal-model-solutions'], source, line, 'reveal-model-solutions', ) if 'grading-mode' in self.options: data['grading_mode'] = self.options['grading-mode'] if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if 'category' in self.options: data['category'] = str(self.options['category']) node.write_yaml(env, name, data, 'exercise') return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) if difficulty: classes.append(u'difficulty-' + difficulty) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', } if 'quiz' in self.options: args[u'data-aplus-quiz'] = u'yes' if 'ajax' in self.options: args[u'data-aplus-ajax'] = u'yes' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append( nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format( self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = {u'_external': True} if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) if 'lti' in self.options: data.update({ u'lti': ensure_unicode(self.options['lti']), u'lti_context_id': ensure_unicode(self.options.get('lti_context_id', u'')), u'lti_resource_link_id': ensure_unicode( self.options.get('lti_resource_link_id', u'')), }) config_title = None config_title = self.options.get('title', config_title) if "radar_tokenizer" in self.options or "radar_minimum_match_tokens" in self.options: data[u'radar_info'] = { u'tokenizer': self.options.get("radar_tokenizer"), u'minimum_match_tokens': self.options.get("radar_minimum_match_tokens"), } category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format(key_title=key_title, config_title=config_title), u'category': u'submit', u'scale_points': points, u'difficulty': difficulty or '', u'max_submissions': self.options.get( 'submissions', data.get('max_submissions', env.config.program_default_submissions)), u'min_group_size': data.get('min_group_size', env.config.default_min_group_size), u'max_group_size': data.get('max_group_size', env.config.default_max_group_size), u'points_to_pass': self.options.get('points-to-pass', data.get('points_to_pass', 0)), }) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if 'category' in self.options: data['category'] = str(self.options['category']) node.write_yaml(env, name, data, 'exercise') return [node]
def parse_chapter(docname, doc, parent, module_meta): for config_file in [e.yaml_write for e in doc.traverse(aplus_nodes.html) if e.has_yaml('exercise')]: config = yaml_writer.read(config_file) if config.get('_external', False): exercise = config.copy() del exercise['_external'] else: exercise = { 'key': config['key'], 'config': config['key'] + '.yaml', 'max_submissions': config.get('max_submissions', 0), 'max_points': config.get('max_points', 0), 'difficulty': config.get('difficulty', ''), 'points_to_pass': config.get('points_to_pass', 0), 'category': config['category'], 'min_group_size': config.get('min_group_size', 1), 'max_group_size': config.get('max_group_size', 1), 'confirm_the_level': config.get('confirm_the_level', False), } allow_assistant_viewing = config.get('allow_assistant_viewing', app.config.allow_assistant_viewing) allow_assistant_grading = config.get('allow_assistant_grading', app.config.allow_assistant_grading) exercise.update({ 'status': config.get('status', 'unlisted'), 'allow_assistant_viewing': allow_assistant_viewing, 'allow_assistant_grading': allow_assistant_grading, }) if 'scale_points' in config: exercise['max_points'] = config.pop('scale_points') # Reveal rules: try exercise config, then module meta, then course config. reveal_submission_feedback = config.get( 'reveal_submission_feedback', module_meta.get( 'reveal-submission-feedback', course_reveal_submission_feedback, ) ) if reveal_submission_feedback: exercise['reveal_submission_feedback'] = reveal_submission_feedback.copy() reveal_model_solutions = config.get( 'reveal_model_solutions', module_meta.get( 'reveal-model-solutions', course_reveal_model_solutions, ) ) if reveal_model_solutions: exercise['reveal_model_solutions'] = reveal_model_solutions.copy() if 'grading_mode' in config: exercise['grading_mode'] = config.pop('grading_mode') parent.append(exercise) if not config['category'] in category_keys: category_keys.append(config['category']) for config_file in [e.yaml_write for e in doc.traverse(aplus_nodes.html) if e.has_yaml('exercisecollection')]: config = yaml_writer.read(config_file) exercise = { 'key': config['key'], 'max_points': config.get('max_points', 0), 'points_to_pass': config.get('points_to_pass', 0), 'target_url': config['target_url'], 'target_category': config['target_category'], 'category': config['category'], 'status': config.get('status', 'unlisted'), 'title': config['title'], } parent.append(exercise) if not config['category'] in category_keys: category_keys.append(config['category']) category = 'chapter' for name,hidden,child in traverse_tocs(app, doc): meta = first_meta(child) status = 'hidden' if 'hidden' in meta else ( 'unlisted' if hidden else 'ready' ) chapter = { 'status': status, 'name': first_title(child), 'static_content': name + '.html', 'category': category, 'use_wide_column': app.config.use_wide_column, 'children': [], } # If the chapter RST file is in a nested directory under the module # directory (e.g., module01/material/chapter.rst instead of # module01/chapter.rst), then the chapter key must contain parts of # the nested directory names in order to be unique within the module. # Different directories could contain files with the same names. key_parts = name.split('/') chapter['key'] = '_'.join(key_parts[1:]) if meta: audience = meta.get('audience') if audience: chapter['audience'] = audience if category in override: chapter.update(override[category]) parent.append(chapter) if not 'chapter' in category_keys: category_keys.append('chapter') parse_chapter(name, child, chapter['children'], module_meta)
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', u'data-aplus-active-element': u'out', u'data-inputs': u''+ self.options.get('inputs', ''), } if 'inputs' not in self.options: raise self.warning("The input list for output '{:s}' is empty.".format(key)) if 'type' in self.options: args['data-type'] = self.options['type'] else: args['data-type'] = 'text' if 'scale-size' in self.options: args['data-scale'] = '' if 'title' in self.options: args['data-title'] = self.options['title'] if 'width' in self.options: args['style'] = 'width:'+ self.options['width'] + ';' if 'height' in self.options: if 'style' not in args: args['style'] = 'height:'+ self.options['height'] + ';' else: args['style'] = args['style'] + 'height:'+ self.options['height'] + ';' if 'clear' in self.options: args['style'] = args['style'] + 'clear:'+ self.options['clear'] + ';' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = { u'_external': True } if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) config_title = None config_title = self.options.get('title', config_title) category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format( key_title=key_title, config_title=config_title ), u'category': u'active elements', u'max_submissions': self.options.get('submissions', data.get('max_submissions', env.config.ae_default_submissions)), }) data.setdefault('status', self.options.get('status', 'unlisted')) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) node.write_yaml(env, name, data, 'exercise') return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) if difficulty: classes.append(u'difficulty-' + difficulty) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', } if 'quiz' in self.options: args[u'data-aplus-quiz'] = u'yes' if 'ajax' in self.options: args[u'data-aplus-ajax'] = u'yes' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = { u'_external': True } if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) if 'lti' in self.options: data.update({ u'lti': ensure_unicode(self.options['lti']), u'lti_context_id': ensure_unicode(self.options.get('lti_context_id', u'')), u'lti_resource_link_id': ensure_unicode(self.options.get('lti_resource_link_id', u'')), }) config_title = None config_title = self.options.get('title', config_title) if "radar_tokenizer" in self.options or "radar_minimum_match_tokens" in self.options: data[u'radar_info'] = { u'tokenizer': self.options.get("radar_tokenizer"), u'minimum_match_tokens': self.options.get("radar_minimum_match_tokens"), } category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format( key_title=key_title, config_title=config_title ), u'category': u'submit', u'scale_points': points, u'difficulty': difficulty or '', u'max_submissions': self.options.get('submissions', data.get('max_submissions', env.config.program_default_submissions)), u'min_group_size': data.get('min_group_size', env.config.default_min_group_size), u'max_group_size': data.get('max_group_size', env.config.default_max_group_size), u'points_to_pass': self.options.get('points-to-pass', data.get('points_to_pass', 0)), }) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if 'category' in self.options: data['category'] = str(self.options['category']) node.write_yaml(env, name, data, 'exercise') return [node]
def parse_chapter(docname, doc, parent): for config_file in [e.yaml_write for e in doc.traverse(aplus_nodes.html) if e.has_yaml(u'exercise')]: config = yaml_writer.read(config_file) if config.get(u'_external', False): exercise = config.copy() del exercise[u'_external'] else: exercise = { u'key': config[u'key'], u'config': config[u'key'] + u'.yaml', u'max_submissions': config.get(u'max_submissions', 0), u'max_points': config.get(u'max_points', 0), u'difficulty': config.get(u'difficulty', ''), u'points_to_pass': config.get(u'points_to_pass', 0), u'category': config[u'category'], u'min_group_size': config.get(u'min_group_size', 1), u'max_group_size': config.get(u'max_group_size', 1), u'confirm_the_level': config.get(u'confirm_the_level', False), } allow_assistant_viewing = config.get(u'allow_assistant_viewing', app.config.allow_assistant_viewing) allow_assistant_grading = config.get(u'allow_assistant_grading', app.config.allow_assistant_grading) exercise.update({ u'status': config.get(u'status', u'unlisted'), u'allow_assistant_viewing': allow_assistant_viewing, u'allow_assistant_grading': allow_assistant_grading, }) if u'scale_points' in config: exercise[u'max_points'] = config.pop(u'scale_points') parent.append(exercise) if not config[u'category'] in category_keys: category_keys.append(config[u'category']) for config_file in [e.yaml_write for e in doc.traverse(aplus_nodes.html) if e.has_yaml(u'exercisecollection')]: config = yaml_writer.read(config_file) exercise = { u'key': config[u'key'], u'max_points': config.get(u'max_points', 0), u'points_to_pass': config.get(u'points_to_pass', 0), u'target_url': config[u'target_url'], u'target_category': config[u'target_category'], u'category': config[u'category'], u'status': config.get(u'status', u'unlisted'), u'title': config[u'title'], } parent.append(exercise) if not config[u'category'] in category_keys: category_keys.append(config[u'category']) category = u'chapter' for name,hidden,child in traverse_tocs(app, doc): meta = first_meta(child) status = u'hidden' if 'hidden' in meta else ( u'unlisted' if hidden else u'ready' ) chapter = { u'status': status, u'name': first_title(child), u'static_content': name + u'.html', u'category': category, u'use_wide_column': app.config.use_wide_column, u'children': [], } # If the chapter RST file is in a nested directory under the module # directory (e.g., module01/material/chapter.rst instead of # module01/chapter.rst), then the chapter key must contain parts of # the nested directory names in order to be unique within the module. # Different directories could contain files with the same names. key_parts = name.split('/') chapter['key'] = '_'.join(key_parts[1:]) if meta: audience = meta.get('audience') if audience: chapter[u'audience'] = yaml_writer.ensure_unicode(audience) if category in override: chapter.update(override[category]) parent.append(chapter) if not u'chapter' in category_keys: category_keys.append(u'chapter') parse_chapter(name, child, chapter[u'children'])
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) if difficulty: classes.append(u'difficulty-' + difficulty) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', } if 'quiz' in self.options: args[u'data-aplus-quiz'] = u'yes' if 'ajax' in self.options: args[u'data-aplus-ajax'] = u'yes' node = aplus_nodes.html(u'div', args) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = { u'_external': True } if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) if 'lti' in self.options: data.update({ u'lti': ensure_unicode(self.options['lti']), u'lti_context_id': ensure_unicode(self.options.get('lti_context_id', u'')), u'lti_resource_link_id': ensure_unicode(self.options.get('lti_resource_link_id', u'')), }) if 'lti_aplus_get_and_post' in self.options: data.update({u'lti_aplus_get_and_post': True}) if 'lti_open_in_iframe' in self.options: data.update({u'lti_open_in_iframe': True}) config_title = None config_title = self.options.get('title', config_title) if "radar_tokenizer" in self.options or "radar_minimum_match_tokens" in self.options: data[u'radar_info'] = { u'tokenizer': self.options.get("radar_tokenizer"), u'minimum_match_tokens': self.options.get("radar_minimum_match_tokens"), } category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format( key_title=key_title, config_title=config_title ), u'category': u'submit', u'scale_points': points, u'difficulty': difficulty or '', u'max_submissions': self.options.get('submissions', data.get('max_submissions', env.config.program_default_submissions)), u'min_group_size': data.get('min_group_size', env.config.default_min_group_size), u'max_group_size': data.get('max_group_size', env.config.default_max_group_size), u'points_to_pass': self.options.get('points-to-pass', data.get('points_to_pass', 0)), }) self.set_assistant_permissions(data) if self.content: self.assert_has_content() nested_parse_with_titles(self.state, self.content, node) # Sphinx can not compile the nested RST into HTML at this stage, hence # the HTML instructions defined in this directive body are added to # the exercise YAML file only at the end of the build. Sphinx calls # the visit functions of the nodes in the last writing phase. # The instructions are added to the YAML file in the depart_html # function in aplus_nodes.py. else: paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) data.setdefault('status', self.options.get('status', 'unlisted')) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if 'category' in self.options: data['category'] = str(self.options['category']) node.write_yaml(env, name, data, 'exercise') return [node]