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), } exercise.update({ u'allow_assistant_grading': False, 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(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 make_index(app, root): course_title = app.config.course_title course_open = app.config.course_open_date course_close = app.config.course_close_date course_late = app.config.default_late_date course_penalty = app.config.default_late_penalty override = app.config.override modules = [] category_keys = [] def get_static_dir(app): i = 0 while i < len(app.outdir) and i < len( app.confdir) and app.outdir[i] == app.confdir[i]: i += 1 outdir = app.outdir.replace("\\", "/") if outdir[i] == '/': i += 1 return outdir[i:] def first_title(doc): titles = doc.traverse(nodes.title) return titles[0].astext() if titles else u'Unnamed' def first_meta(doc): metas = doc.traverse(directives.meta.aplusmeta) return metas[0].options if metas else {} # Tries to parse date from natural text. def parse_date(src): parts = src.split(u' ', 1) d = parts[0] t = parts[1] if len(parts) > 1 else '' if re.match(r'^\d\d.\d\d.\d\d\d\d$', d): ds = d.split('.') d = ds[2] + u'-' + ds[1] + u'-' + ds[0] elif not re.match(r'^\d\d\d\d-\d\d-\d\d$', d): raise SphinxError(u'Invalid date ' + d) if not re.match(r'^\d\d(:\d\d(:\d\d)?)?$', t): t = u'12:00' return d + u' ' + t def parse_float(src, default): return float(src) if src else default # Recursive chapter parsing. 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']) # Read title from document. if not course_title: course_title = first_title(root) # Traverse the documents using toctree directives. title_date_re = re.compile(r'.*\(DL (.+)\)') for docname, hidden, doc in traverse_tocs(app, root): title = first_title(doc) title_date_match = title_date_re.match(title) meta = first_meta(doc) status = u'hidden' if 'hidden' in meta else ( u'unlisted' if hidden else u'ready') open_src = meta.get('open-time', course_open) close_src = meta.get( 'close-time', title_date_match.group(1) if title_date_match else course_close) late_src = meta.get('late-time', course_late) module = { u'key': docname.split(u'/')[0], u'status': status, u'name': title, u'children': [], } if open_src: module[u'open'] = parse_date(open_src) if close_src: module[u'close'] = parse_date(close_src) if late_src: module[u'late_close'] = parse_date(late_src) module[u'late_penalty'] = parse_float( meta.get('late-penalty', course_penalty), 0.0) modules.append(module) parse_chapter(docname, doc, module[u'children']) # Create categories. category_names = app.config.category_names categories = { key: { u'name': category_names.get(key, key), } for key in category_keys } for key in ['chapter', 'feedback']: if key in categories: categories[key][u'status'] = u'nototal' # Build configuration index. index = { u'name': course_title, u'language': app.config.language, u'static_dir': get_static_dir(app), u'modules': modules, u'categories': categories, } if course_open: index[u'start'] = parse_date(course_open) if course_close: index[u'end'] = parse_date(course_close) return index
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 make_index(app, root, language=''): # metadata is defined in the field list of the RST document before any section # and other content. The master_doc is the main index.rst file of the course. # The syntax for field lists in RST is like this: # :course-start: 2019-09-16 12:00 course_meta = app.env.metadata[app.config.master_doc] course_title = app.config.course_title course_open = course_meta.get('course-start', app.config.course_open_date) course_close = course_meta.get('course-end', app.config.course_close_date) # default late deadline for modules: if defined, all modules allow late submissions course_late = course_meta.get('course-default-late', app.config.default_late_date) course_penalty = course_meta.get('course-default-late-penalty', app.config.default_late_penalty) override = app.config.override course_reveal_submission_feedback = parse_reveal_rule( app.config.reveal_submission_feedback, 'conf.py', None, 'reveal_submission_feedback', ) course_reveal_model_solutions = parse_reveal_rule( app.config.reveal_model_solutions, 'conf.py', None, 'reveal_model_solutions', ) modules = [] category_keys = [] def get_static_dir(app): i = 0 while i < len(app.outdir) and i < len(app.confdir) and app.outdir[i] == app.confdir[i]: i += 1 outdir = app.outdir.replace("\\", "/") if outdir[i] == '/': i += 1 return outdir[i:] def first_title(doc): titles = list(doc.traverse(nodes.title)) return titles[0].astext() if titles else 'Unnamed' def first_meta(doc): metas = list(doc.traverse(directives.meta.aplusmeta)) return metas[0].options if metas else {} # Tries to parse date from natural text. def parse_date(src, allow_empty=False): if allow_empty and not src: return None parts = src.split(' ', 1) d = parts[0] t = parts[1] if len(parts) > 1 else '' if re.match(r'^\d\d.\d\d.\d\d\d\d$', d): ds = d.split('.') d = ds[2] + '-' + ds[1] + '-' + ds[0] elif not re.match(r'^\d\d\d\d-\d\d-\d\d$', d): raise SphinxError('Invalid date ' + d) if not re.match(r'^\d\d(:\d\d(:\d\d)?)?$', t): t = '12:00' return d + ' ' + t def parse_float(src, default): return float(src) if src else default # Recursive chapter parsing. 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) # Read title from document. if not course_title: course_title = first_title(root) # Traverse the documents using toctree directives. title_date_re = re.compile(r'.*\(DL (.+)\)') for docname,hidden,doc in traverse_tocs(app, root): title = first_title(doc) title_date_match = title_date_re.match(title) meta = first_meta(doc) status = 'hidden' if 'hidden' in meta else ( 'unlisted' if hidden else 'ready' ) read_open_src = meta.get('read-open-time', None) open_src = meta.get('open-time', course_open) close_src = meta.get('close-time', title_date_match.group(1) if title_date_match else course_close) late_src = meta.get('late-time', course_late) introduction = meta.get('introduction', None) module = { # modules01/index -> modules01 # modules/01/index -> modules_01 # modules/01/n/index -> modules_01_n # ... 'key': docname if '/' not in docname else '_'.join(docname.split('/')[:-1]), 'status': status, 'name': title, 'points_to_pass': meta.get('points-to-pass', 0), 'children': [], } if read_open_src: module['read-open'] = parse_date(read_open_src) if open_src: module['open'] = parse_date(open_src) if close_src: module['close'] = parse_date(close_src) if late_src: module['late_close'] = parse_date(late_src) module['late_penalty'] = parse_float(meta.get('late-penalty', course_penalty), 0.0) if introduction is not None: module['introduction'] = introduction modules.append(module) parse_chapter(docname, doc, module['children'], meta) # Create categories. category_names = app.config.category_names categories = { key: { 'name': category_names.get(key, key), } for key in category_keys } for key in ['chapter', 'feedback']: if key in categories: categories[key]['status'] = 'nototal' # Build configuration index. index = { 'name': course_title, 'static_dir': get_static_dir(app), 'modules': modules, 'categories': categories, } index['language'] = language if language else app.config.language course_enrollment_start = course_meta.get('enrollment-start') course_enrollment_end = course_meta.get('enrollment-end') course_lifesupport_time = course_meta.get('lifesupport-time') course_archive_time = course_meta.get('archive-time') if course_open: index['start'] = parse_date(course_open) if course_close: index['end'] = parse_date(course_close) if course_enrollment_start is not None: # None check separates the cases: # 1) user inputs an empty value and it should be set into the YAML, # 2) user does not define any value and no value should be set in YAML index['enrollment_start'] = parse_date(course_enrollment_start, True) if course_enrollment_end is not None: index['enrollment_end'] = parse_date(course_enrollment_end, True) if course_lifesupport_time is not None: index['lifesupport_time'] = parse_date(course_lifesupport_time, True) if course_archive_time is not None: index['archive_time'] = parse_date(course_archive_time, True) if course_meta.get('view-content-to'): index['view_content_to'] = course_meta.get('view-content-to') if course_meta.get('enrollment-audience'): index['enrollment_audience'] = course_meta.get('enrollment-audience') if course_meta.get('index-mode'): index['index_mode'] = course_meta.get('index-mode') if course_meta.get('content-numbering'): index['content_numbering'] = course_meta.get('content-numbering') if course_meta.get('module-numbering'): index['module_numbering'] = course_meta.get('module-numbering') if course_meta.get('numerate-ignoring-modules') is not None: index['numerate_ignoring_modules'] = \ True if course_meta.get('numerate-ignoring-modules', False) not in ( False, 'false', 'False', 'no', 'No' ) else False head_urls = course_meta.get('course-head-urls', app.config.course_head_urls) if head_urls is not None: # If the value is None, it is not set to the index.yaml nor aplus-json at all. # If the value is an empty list, it is still part of the index.yaml # and could be used to override a previous truthy value. if isinstance(head_urls, str): # convert to a list and remove empty strings head_urls = list(filter(None, head_urls.split('\n'))) index['head_urls'] = head_urls if course_meta.get('course-description') is not None: index['course_description'] = course_meta.get('course-description') if course_meta.get('course-footer') is not None: index['course_footer'] = course_meta.get('course-footer') return index
def write(app, exception): ''' Writes the table of contents level configuration. ''' if exception: return course_title = app.config.course_title course_open = app.config.course_open_date course_close = app.config.course_close_date course_late = app.config.default_late_date course_penalty = app.config.default_late_penalty override = app.config.override modules = [] category_keys = [] def traverse_tocs(doc): names = [] for toc in doc.traverse(addnodes.toctree): hidden = toc.attributes['hidden'] for _, docname in toc.get('entries', []): names.append((docname, hidden)) return [(name, hidden, app.env.get_doctree(name)) for name, hidden in names] def first_title(doc): titles = doc.traverse(nodes.title) return titles[0].astext() if titles else u'Unnamed' def first_meta(doc): metas = doc.traverse(directives.meta.aplusmeta) return metas[0].options if metas else {} # Tries to parse date from natural text. def parse_date(src): parts = src.split(u' ', 1) d = parts[0] t = parts[1] if len(parts) > 1 else '' if re.match(r'^\d\d.\d\d.\d\d\d\d$', d): ds = d.split('.') d = ds[2] + u'-' + ds[1] + u'-' + ds[0] elif not re.match(r'^\d\d\d\d-\d\d-\d\d$', d): raise SphinxError(u'Invalid date ' + d) if not re.match(r'^\d\d(:\d\d(:\d\d)?)?$', t): t = u'12:00' return d + u' ' + t def parse_float(src, default): return float(src) if src else default # Recursive chapter parsing. 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), } exercise.update({ u'allow_assistant_grading': False, 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(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']) root = app.env.get_doctree(app.config.master_doc) if not course_title: course_title = first_title(root) # Traverse the documents using toctree directives. app.info('Traverse document elements to write configuration index.') title_date_re = re.compile(r'.*\(DL (.+)\)') for docname, hidden, doc in traverse_tocs(root): title = first_title(doc) title_date_match = title_date_re.match(title) meta = first_meta(doc) status = u'hidden' if 'hidden' in meta else ( u'unlisted' if hidden else u'ready') open_src = meta.get('open-time', course_open) close_src = meta.get( 'close-time', title_date_match.group(1) if title_date_match else course_close) late_src = meta.get('late-time', course_late) module = { u'key': docname.split(u'/')[0], u'status': status, u'name': title, u'children': [], } if open_src: module[u'open'] = parse_date(open_src) if close_src: module[u'close'] = parse_date(close_src) if late_src: module[u'late_close'] = parse_date(late_src) module[u'late_penalty'] = parse_float( meta.get('late-penalty', course_penalty), 0.0) modules.append(module) parse_chapter(docname, doc, module[u'children']) # Create categories. category_names = app.config.category_names categories = { key: { u'name': category_names.get(key, key), } for key in category_keys } for key in ['chapter', 'feedback']: if key in categories: categories[key][u'status'] = u'nototal' # Get relative out dir. i = 0 while i < len(app.outdir) and i < len( app.confdir) and app.outdir[i] == app.confdir[i]: i += 1 outdir = app.outdir.replace("\\", "/") if outdir[i] == '/': i += 1 outdir = outdir[i:] # Write the configuration index. config = { u'name': course_title, u'language': app.config.language, u'static_dir': outdir, u'modules': modules, u'categories': categories, } if course_open: config[u'start'] = parse_date(course_open) if course_close: config[u'end'] = parse_date(course_close) # Append directly configured content. 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(config, yaml_writer.read(path)) yaml_writer.write(yaml_writer.file_path(app.env, 'index'), config) # Mark links to other modules. app.info('Retouch all files to append chapter link attributes.') keys = [m['key'] for m in modules] keys.extend(['toc', 'user', 'account']) for html_file in html_tools.walk(os.path.dirname(app.outdir)): html_tools.annotate_file_links(html_file, [u'a'], [u'href'], keys, u'data-aplus-chapter="yes" ')
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'])