Ejemplo n.º 1
0
    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'])
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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" ')
Ejemplo n.º 6
0
    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'])