コード例 #1
0
    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
コード例 #2
0
    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
コード例 #3
0
    def create_question(self, title_text=None, points=True):
        env = self.state.document.settings.env
        env.question_count += 1

        # Create base element and data.
        node = aplus_nodes.html('div', {
            'class': ' '.join(self.get_classes()),
        })
        data = {
            'type': self.grader_field_type(),
            'extra_info': self.get_extra_info(),
        }
        key = self.options.get('key', None)
        if key:
            data['key'] = key

        # Add title.
        if not title_text is None:
            data['title'] = title_text
        elif env.questionnaire_is_feedback:
            data['title'] = title_text = ''
        else:
            # "#" in the question title is converted to a number in the MOOC-grader.
            # The questions of a "pick randomly" questionnaire should be numbered
            # in the MOOC-grader since they are randomly selected.
            postfix = '{#}' if env.aplus_pick_randomly_quiz else "{:d}".format(
                env.question_count)
            data['title|i18n'] = translations.opt('question', postfix=postfix)
            title_text = "{} {:d}".format(translations.get(env, 'question'),
                                          env.question_count)
        if title_text:
            title = aplus_nodes.html('label', {})
            title.append(nodes.Text(title_text))
            node.append(title)

        # Add configuration.
        if points and len(self.arguments) > 0:
            question_points = int(self.arguments[0])
            data['points'] = question_points
            env.aplus_quiz_total_points += question_points
            if env.aplus_pick_randomly_quiz:
                if env.aplus_single_question_points is None:
                    env.aplus_single_question_points = question_points
                else:
                    if env.aplus_single_question_points != question_points:
                        source, line = self.state_machine.get_source_and_line(
                            self.lineno)
                        logger.warning(
                            "Each question must have equal points when "
                            "the questionnaire uses the 'pick randomly' option.",
                            location=(source, line))

        if 'required' in self.options:
            data['required'] = True
        node.set_yaml(data, 'question')

        return env, node, data
コード例 #4
0
    def create_question(self, title_text=None, points=True):
        env = self.state.document.settings.env
        env.question_count += 1

        # Create base element and data.
        node = aplus_nodes.html(u'div', {
            u'class': u' '.join(self.get_classes()),
        })
        data = {
            u'type': self.grader_field_type(),
            u'extra_info': self.get_extra_info(),
        }
        key = self.options.get('key', None)
        if key:
            data[u'key'] = yaml_writer.ensure_unicode(key)

        # Add title.
        if not title_text is None:
            data[u'title'] = title_text
        elif env.questionnaire_is_feedback:
            data[u'title'] = title_text = u''
        else:
            data[u'title|i18n'] = translations.opt('question',
                                                   postfix=u" {:d}".format(
                                                       env.question_count))
            title_text = u"{} {:d}".format(translations.get(env, 'question'),
                                           env.question_count)
        if title_text:
            title = aplus_nodes.html(u'label', {})
            title.append(nodes.Text(title_text))
            node.append(title)

        # Add configuration.
        if points and len(self.arguments) > 0:
            question_points = int(self.arguments[0])
            data['points'] = question_points
            env.aplus_quiz_total_points += question_points
            if env.aplus_pick_randomly_quiz:
                if env.aplus_single_question_points is None:
                    env.aplus_single_question_points = question_points
                else:
                    if env.aplus_single_question_points != question_points:
                        source, line = self.state_machine.get_source_and_line(
                            self.lineno)
                        logger.warning(
                            "Each question must have equal points when "
                            "the questionnaire uses the 'pick randomly' option.",
                            location=(source, line))

        if 'required' in self.options:
            data[u'required'] = True
        node.set_yaml(data, u'question')

        return env, node, data
コード例 #5
0
    def run(self):

        if len(self.arguments) > 0:
            key = self.arguments[0]
        else:
            raise SphinxError('Missing active element input id')

        env = self.state.document.settings.env
        name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key)
        override = env.config.override

        classes = []
        if 'class' in self.options:
            classes.extend(self.options['class'])

        # Add document nodes.
        args = {
            u'class': u' '.join(classes),
            u'data-aplus-active-element': u'in',
            u'id': u'' + key,
        }

        if 'title' in self.options:
            args['data-title'] = self.options['title']

        if 'default' in self.options:
            args['data-default'] = self.options['default']

        if 'type' in self.options:
            args['data-type'] = self.options['type']

        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, 'active_element_placeholder')))
        node.append(paragraph)

        return [node]
コード例 #6
0
    def run(self):
        self.assert_has_content()
        node = nodes.container()
        name = self.arguments[0]
        # TODO: maybe use sphinx/docutils node instead of aplus_node for the link
        link = aplus_nodes.html(u'a', {
            u'href':u'#' + name, 
            u'data-toggle':u'collapse'})
        label = 'Show/Hide'
        if 'label' in self.options:
            label = self.options['label']

        # Container for the hidden content
        classes = ['collapse']
        if 'visible' in self.options:
            classes.append('in')
        collapsible = nodes.container('\n'.join(self.content))
        collapsible['classes'].extend(classes)
        self.options['name'] = name
        self.add_name(collapsible)

        link.append(nodes.Text(label))
        node.append(link)
        node.append(collapsible)
        self.state.nested_parse(self.content, self.content_offset, collapsible)

        return [node]
コード例 #7
0
    def run(self):
        self.assert_has_content()
        node = nodes.container()
        name = self.arguments[0]
        # TODO: maybe use sphinx/docutils node instead of aplus_node for the link
        link = aplus_nodes.html('a', {
            'href':'#' + name, 
            'data-toggle':'collapse'})
        label = 'Show/Hide'
        if 'label' in self.options:
            label = self.options['label']

        # Container for the hidden content
        classes = ['collapse']
        if 'visible' in self.options:
            classes.append('in')
        collapsible = nodes.container('\n'.join(self.content))
        collapsible['classes'].extend(classes)
        self.options['name'] = name
        self.add_name(collapsible)

        link.append(nodes.Text(label))
        node.append(link)
        node.append(collapsible)
        self.state.nested_parse(self.content, self.content_offset, collapsible)

        return [node]
コード例 #8
0
 def run(self):
     # This directive is obsolete, AgreeItems can be placed alone.
     classes = [u'form-group']
     if 'class' in self.options:
         classes.extend(self.options['class'])
     node = aplus_nodes.html(u'div', { u'class': u" ".join(classes) })
     nested_parse_with_titles(self.state, self.content, node)
     return  [node]
コード例 #9
0
 def run(self):
     # This directive is obsolete, AgreeItems can be placed alone.
     classes = [u'form-group']
     if 'class' in self.options:
         classes.extend(self.options['class'])
     node = aplus_nodes.html(u'div', {u'class': u" ".join(classes)})
     nested_parse_with_titles(self.state, self.content, node)
     return [node]
コード例 #10
0
    def add_feedback(self, node, data, paragraph):
        if not paragraph:
            return

        # Add feedback node for rendering without writing to file.
        data['feedback'] = ('#!children', 'feedback')
        feedbacks = aplus_nodes.html('p', {'class': 'feedback-holder'},
                                     no_write=True)

        for i, line in slicer(paragraph):
            if not '§' in line[0]:
                raise SphinxError('Feedback separator § exptected: {}'.format(
                    line[0]))
            value, content = line[0].split('§', 1)
            value = value.strip()
            line[0] = content.strip()
            isnot = False
            compare_regexp = False
            if value.startswith('!'):
                isnot = True
                value = value[1:]
            if value.startswith('regexp:'):
                compare_regexp = True
                value = value[7:]

            # Create document elements.
            hint = aplus_nodes.html('div')
            text = aplus_nodes.html('p', {})
            text.store_html('hint')
            nested_parse_with_titles(self.state, line, text)
            hint.append(text)
            feedbacks.append(hint)

            # Add configuration data.
            fbdata = {
                'value': value,
                'label': ('#!html', 'hint'),
            }
            if isnot:
                fbdata['not'] = True
            if compare_regexp:
                fbdata['compare_regexp'] = True
            hint.set_yaml(fbdata, 'feedback')

        node.append(feedbacks)
コード例 #11
0
 def generate_options(self, env, node):
     options = []
     for i, key in enumerate(['agreement4', 'agreement3', 'agreement2', 'agreement1', 'agreement0']):
         options.append({
             u'value': 4 - i,
             u'label|i18n': translations.opt(key),
         })
         choice = aplus_nodes.html(u'div', {u'class':u'radio'})
         label = aplus_nodes.html(u'label', {})
         label.append(aplus_nodes.html(u'input', {
             u'type': u'radio',
             u'name': u'field_{:d}'.format(env.question_count - 1),
             u'value': 4 - i,
         }))
         label.append(nodes.Text(translations.get(env, key)))
         choice.append(label)
         node.append(choice)
     return options
コード例 #12
0
 def generate_options(self, env, node):
     options = []
     for i, key in enumerate(['agreement4', 'agreement3', 'agreement2', 'agreement1', 'agreement0']):
         options.append({
             u'value': 4 - i,
             u'label|i18n': translations.opt(key),
         })
         choice = aplus_nodes.html(u'div', {u'class':u'radio'})
         label = aplus_nodes.html(u'label', {})
         label.append(aplus_nodes.html(u'input', {
             u'type': u'radio',
             u'name': u'field_{:d}'.format(env.question_count - 1),
             u'value': 4 - i,
         }))
         label.append(nodes.Text(translations.get(env, key)))
         choice.append(label)
         node.append(choice)
     return options
コード例 #13
0
    def add_instructions(self, node, data, plain_content):
        if not plain_content:
            return

        parent = aplus_nodes.html(u'div', {})
        parent.store_html(u'more')
        nested_parse_with_titles(self.state, plain_content, parent)
        node.append(parent)

        data[u'more'] = (u'#!html', u'more')
コード例 #14
0
    def add_instructions(self, node, data, plain_content):
        if not plain_content:
            return

        parent = aplus_nodes.html(u'div', {})
        parent.store_html(u'more')
        nested_parse_with_titles(self.state, plain_content, parent)
        node.append(parent)

        data[u'more'] = (u'#!html', u'more')
コード例 #15
0
ファイル: row.py プロジェクト: skulonen/a-plus-rst-tools
    def run(self):
        self.assert_has_content()
        container_class = 'row'
        row_container_opts = {
            'class': container_class,
        }

        row_container = aplus_nodes.html('div', row_container_opts)
        self.state.nested_parse(self.content, self.content_offset, row_container)

        return [row_container]
コード例 #16
0
    def create_question(self, title_text=None, points=True):
        env = self.state.document.settings.env
        env.question_count += 1

        # Create base element and data.
        node = aplus_nodes.html(u'div', {
            u'class': u' '.join(self.get_classes()),
        })
        data = {
            u'type': self.grader_field_type(),
            u'extra_info': self.get_extra_info(),
        }
        key = self.options.get('key', None)
        if key:
            data[u'key'] = yaml_writer.ensure_unicode(key)

        # Add title.
        if not title_text is None:
            data[u'title'] = title_text
        elif env.questionnaire_is_feedback:
            data[u'title'] = title_text = u''
        else:
            data[u'title|i18n'] = translations.opt('question',
                                                   postfix=u" {:d}".format(
                                                       env.question_count))
            title_text = u"{} {:d}".format(translations.get(env, 'question'),
                                           env.question_count)
        if title_text:
            title = aplus_nodes.html(u'label', {})
            title.append(nodes.Text(title_text))
            node.append(title)

        # Add configuration.
        if points and len(self.arguments) > 0:
            env.aplus_quiz_total_points += int(self.arguments[0])
            data[u'points'] = int(self.arguments[0])
        if 'required' in self.options:
            data[u'required'] = True
        node.set_yaml(data, u'question')

        return env, node, data
コード例 #17
0
    def add_feedback(self, node, data, paragraph):
        if not paragraph:
            return

        # Add feedback node for rendering without writing to file.
        data[u'feedback'] = (u'#!children', u'feedback')
        feedbacks = aplus_nodes.html(u'p', {u'class':u'feedback-holder'}, no_write=True)

        for i,line in slicer(paragraph):
            if not u'§' in line[0]:
                raise SphinxError(u'Feedback separator § exptected: {}'.format(line[0]))
            value,content = line[0].split(u'§', 1)
            value = value.strip()
            line[0] = content.strip()
            isnot = False
            if value.startswith(u'!'):
                isnot = True
                value = value[1:]

            # Create document elements.
            hint = aplus_nodes.html(u'div')
            text = aplus_nodes.html(u'p', {})
            text.store_html('hint')
            nested_parse_with_titles(self.state, line, text)
            hint.append(text)
            feedbacks.append(hint)

            # Add configuration data.
            fbdata = {
                u'value': value,
                u'label': (u'#!html', u'hint'),
            }
            if isnot:
                fbdata[u'not'] = True
            hint.set_yaml(fbdata, u'feedback')

        node.append(feedbacks)
コード例 #18
0
    def create_question(self, title_text=None, points=True):
        env = self.state.document.settings.env
        env.question_count += 1

        # Create base element and data.
        node = aplus_nodes.html(u'div', {
            u'class': u' '.join(self.get_classes()),
        })
        data = {
            u'type': self.grader_field_type(),
            u'extra_info': self.get_extra_info(),
        }
        key = self.options.get('key', None)
        if key:
            data[u'key'] = yaml_writer.ensure_unicode(key)

        # Add title.
        if not title_text is None:
            data[u'title'] = title_text
        elif env.questionnaire_is_feedback:
            data[u'title'] = title_text = u''
        else:
            data[u'title|i18n'] = translations.opt('question', postfix=u" {:d}".format(env.question_count))
            title_text = u"{} {:d}".format(translations.get(env, 'question'), env.question_count)
        if title_text:
            title = aplus_nodes.html(u'label', {})
            title.append(nodes.Text(title_text))
            node.append(title)

        # Add configuration.
        if points and len(self.arguments) > 0:
            data[u'points'] = int(self.arguments[0])
        if 'required' in self.options:
            data[u'required'] = True
        node.set_yaml(data, u'question')

        return env, node, data
コード例 #19
0
    def run(self):

        key, difficulty, points = self.extract_exercise_arguments()
        env = self.state.document.settings.env

        errors = []

        if not 'target_category' in self.options:
            errors.append('Error in {} directive: Missing target_category.'.format(self.name))

        if not 'target_url' in self.options:
            errors.append('Error in {} directive: Missing target_url.'.format(self.name))

        assert not errors, '\n'.join(errors)

        name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key)


        node = aplus_nodes.html(u'div',{
            u'class': 'exercisecollection',
            u'data-aplus-exercise': u'yes',
        })



        data = {
            u'key': name,
            u'category': self.options.get('category', 'prerequisit'),
            u'max_points': self.options.get('max_points', 10),
            u'points_to_pass': self.options.get('points-to-pass', 0),
            u'target_url': self.options.get('target_url', None),
            u'target_category': self.options.get('target_category', None),
            u'title': self.arguments[0]
        }

        node.write_yaml(env, name, data, 'exercisecollection')

        return [node]
コード例 #20
0
    def run(self):

        key, difficulty, points = self.extract_exercise_arguments()
        env = self.state.document.settings.env

        errors = []

        if not 'target_category' in self.options:
            errors.append(
                'Error in {} directive: Missing target_category.'.format(
                    self.name))

        if not 'target_url' in self.options:
            errors.append('Error in {} directive: Missing target_url.'.format(
                self.name))

        assert not errors, '\n'.join(errors)

        name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key)

        node = aplus_nodes.html(u'div', {
            u'class': 'exercisecollection',
            u'data-aplus-exercise': u'yes',
        })

        data = {
            u'key': name,
            u'category': self.options.get('category', 'prerequisit'),
            u'max_points': self.options.get('max_points', 10),
            u'points_to_pass': self.options.get('points-to-pass', 0),
            u'target_url': self.options.get('target_url', None),
            u'target_category': self.options.get('target_category', None),
            u'title': self.arguments[0]
        }

        node.write_yaml(env, name, data, 'exercisecollection')

        return [node]
コード例 #21
0
    def run(self):

        key, difficulty, points = self.extract_exercise_arguments()
        env = self.state.document.settings.env

        errors = []

        for option in ['collection_category', 'max_points']:
            if option not in self.options:

                # Because warning, error and severe don't make any difference.
                errors.append(
                    'Error in {} directive: Missing option {}.'.format(
                        self.name, option))
                #self.severe('Error in {} directive: Missing option {}.'.format(self.name, option))

        if ('collection_course' in self.options) == ('collection_url'
                                                     in self.options):
            errors.append(
                'Error in {} directive: Must contain either "collection_course" or "collection_url" option.'
                .format(self.name))

        if ('collection_course' in self.options) and (not ';' in str(
                self.options.get('collection_course'))):
            # Because warning, error and severe don't make any difference.
            errors.append(
                'Error in {} directive: collection_course must be in format "<course>;<instance>"'
                .format(self.name))
            #self.reporter.severe('Error in {} directive: collection_course must be in format "<course>;<instance>".'.format(self.name))

        if errors:
            assert False, '\n'.join(errors)

        if 'max_points' in self.options:
            points = int(self.options.get('max_points'))
        else:
            points = 10

        if 'category' in self.options:
            category = str(self.options.get('category'))
        else:
            category = 'Prerequisit'

        name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key)

        node = aplus_nodes.html(u'div', {
            u'class': 'exercisecollection',
            u'data-aplus-exercise': u'yes',
        })

        data = {
            u'key': name,
            u'category': category,
            u'max_points': points,
            u'points_to_pass': self.options.get('points-to-pass'),
            u'collection_course': None,
            u'collection_url': None,
            u'collection_category': self.options.get('collection_category'),
        }
        if 'collection_course' in self.options:
            data[u'collection_course'] = self.options.get('collection_course')
        else:
            data[u'collection_url'] = self.options.get('collection_url')

        node.write_yaml(env, name, data, 'exercisecollection')

        return [node]
コード例 #22
0
    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]
コード例 #23
0
    def run(self):
        self.assert_has_content()
        key, difficulty, points = self.extract_exercise_arguments()

        # Parse options.
        classes = ['exercise']
        is_feedback = False
        if 'chapter-feedback' in self.options:
            classes.append('chapter-feedback')
            is_feedback = True
        if 'weekly-feedback' in self.options:
            classes.append('weekly-feedback')
            is_feedback = True
        if 'appendix-feedback' in self.options:
            classes.append('appendix-feedback')
            is_feedback = True
        if 'course-feedback' in self.options:
            classes.append('course-feedback-questionnaire')
            is_feedback = True
        if 'feedback' in self.options:
            is_feedback = True
        if is_feedback:
            key = 'feedback'
            category = 'feedback'
            classes.append('feedback')
        else:
            category = 'questionnaire'
            if difficulty:
                classes.append('difficulty-' + difficulty)

        if 'category' in self.options:
            category = str(self.options.get('category'))

        env = self.state.document.settings.env
        name = "{}_{}".format(env.docname.replace('/', '_'), key)
        override = env.config.override

        env.questionnaire_is_feedback = is_feedback
        env.question_count = 0
        env.aplus_single_question_points = None
        env.aplus_quiz_total_points = 0
        env.aplus_pick_randomly_quiz = 'pick_randomly' in self.options
        env.aplus_random_question_exists = False

        # Create document elements.
        node = aplus_nodes.html('div', {
            'class': ' '.join(classes),
            'data-aplus-exercise': 'yes',
        })
        form = aplus_nodes.html('form', {
            'action': key,
            'method': 'post',
        })
        nested_parse_with_titles(self.state, self.content, form)

        submit = aplus_nodes.html('input', {
            'type': 'submit',
            'value': translations.get(env, 'submit'),
            'class': 'btn btn-primary',
        },
                                  skip_html=True)
        form.append(submit)
        node.append(form)

        # Write configuration file.
        data = {
            'key':
            name,
            'category':
            category,
            'difficulty':
            difficulty or '',
            'max_submissions':
            self.options.get(
                'submissions', 0 if is_feedback else
                env.config.questionnaire_default_submissions),
            'min_group_size':
            1 if is_feedback else env.config.default_min_group_size,
            'max_group_size':
            1 if is_feedback else env.config.default_max_group_size,
            'points_to_pass':
            self.options.get('points-to-pass', 0),
            'feedback':
            is_feedback,
            'view_type':
            'access.types.stdsync.createForm',
            'status':
            self.options.get('status', 'unlisted'),
            'fieldgroups': [{
                'title': '',
                'fields': ('#!children', None),
            }],
            # 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('\\', '/'),
        }

        meta_data = env.metadata[env.app.config.master_doc]
        # Show the model answer after the last submission.
        if 'reveal-model-at-max-submissions' in self.options:
            data['reveal_model_at_max_submissions'] = str_to_bool(
                self.options['reveal-model-at-max-submissions'])
        else:
            default_reveal = str_to_bool(
                meta_data.get(
                    'questionnaire-default-reveal-model-at-max-submissions',
                    'false'),
                error_msg_prefix=env.app.config.master_doc +
                " questionnaire-default-reveal-model-at-max-submissions: ")
            if default_reveal:
                data['reveal_model_at_max_submissions'] = default_reveal
        # Show the model answer after the module deadline.
        if 'show-model' in self.options:
            data['show_model_answer'] = str_to_bool(self.options['show-model'])
        else:
            show_default = str_to_bool(
                meta_data.get('questionnaire-default-show-model', 'true'),
                error_msg_prefix=env.app.config.master_doc +
                " questionnaire-default-show-model: ")
            if not show_default:
                data['show_model_answer'] = show_default

        if env.aplus_pick_randomly_quiz:
            pick_randomly = self.options.get('pick_randomly', 0)
            if pick_randomly < 1:
                source, line = self.state_machine.get_source_and_line(
                    self.lineno)
                raise SphinxError(
                    source + ": line " + str(line) +
                    "\nNumber of fields to sample randomly should be greater than zero "
                    "(option pick_randomly in the questionnaire directive).")
            data['fieldgroups'][0]['pick_randomly'] = pick_randomly
            if 'preserve-questions-between-attempts' in self.options:
                data['fieldgroups'][0]['resample_after_attempt'] = False
        elif not env.aplus_random_question_exists:
            # The HTML attribute data-aplus-quiz makes the A+ frontend show the
            # questionnaire feedback in place of the exercise description once
            # the student has submitted at least once. In randomized questionnaires,
            # the same form may not be submitted again due to one-time use nonce
            # values, hence the attribute must not be used in randomized
            # questionnaires.
            node.attributes['data-aplus-quiz'] = 'yes'

        if 'autosave' in self.options or env.config.enable_autosave:
            node.attributes['data-aplus-autosave'] = 'yes'

        self.set_assistant_permissions(data)

        points_set_in_arguments = len(
            self.arguments) == 2 and difficulty != self.arguments[1]

        if env.aplus_pick_randomly_quiz:
            calculated_max_points = (self.options.get('pick_randomly') *
                                     env.aplus_single_question_points
                                     if env.aplus_single_question_points
                                     is not None else 0)
        else:
            calculated_max_points = env.aplus_quiz_total_points

        if calculated_max_points == 0 and is_feedback:
            data['max_points'] = points
        else:
            if points_set_in_arguments and calculated_max_points != points:
                source, line = self.state_machine.get_source_and_line(
                    self.lineno)
                raise SphinxError(
                    source + ": line " + str(line) +
                    "\nThe points of the questions in the questionnaire must add up to the total points of the questionnaire!"
                )
            data['max_points'] = calculated_max_points

        if 'title' in self.options:
            data['title'] = self.options.get('title')
        else:
            data['title|i18n'] = translations.opt(
                'feedback') if is_feedback else translations.opt(
                    'exercise', postfix=" {}".format(key))

        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 not 'no-override' in self.options and category in override:
            data.update(override[category])
            if 'url' in data:
                data['url'] = data['url'].format(key=name)

        form.write_yaml(env, name, data, 'exercise')

        return [node]
コード例 #24
0
    def run(self):
        self.assert_has_content()

        # Detect paragraphs: any number of plain content, choices and optional feedback.
        empty_lines = list(loc for loc, line in enumerate(self.content)
                           if line == '')
        plain_content = None
        choices = []
        feedback = []
        if len(empty_lines) > 0:
            last = self.content[(empty_lines[-1] + 1):]

            def split_second_last(empty_lines):
                if len(empty_lines) > 1:
                    return self.content[:empty_lines[-2]], self.content[(
                        empty_lines[-2] + 1):empty_lines[-1]]
                else:
                    return None, self.content[:empty_lines[-1]]

            # Backwards compatibility for skipping feedback paragraph.
            if len(last) == 1 and last[0].startswith(
                    'I hereby declare that no feedback '):
                plain_content, choices = split_second_last(empty_lines)
            elif all('§' in line for line in last):
                plain_content, choices = split_second_last(empty_lines)
                feedback = last
            else:
                plain_content = self.content[:empty_lines[-1]]
                choices = last
        else:
            choices = self.content

        # Create question.
        env, node, data = self.create_question()
        self.add_instructions(node, data, plain_content)
        data['options'] = ('#!children', 'option')
        if 'partial-points' in self.options:
            data['partial_points'] = True

        dropdown = None
        if self.grader_field_type() == 'dropdown':
            # The HTML select element has a different structure compared
            # to the input elements (radio buttons and checkboxes).
            dropdown = aplus_nodes.html(
                'select', {
                    'name': 'field_{:d}'.format(env.question_count - 1),
                })

        correct_count = 0
        # Travel all answer options.
        for i, line in slicer(choices):

            # Split choice key off.
            key, content = line[0].split(' ', 1)
            key = key.strip()
            line[0] = content.strip()

            # Trim the key.
            correct = False
            selected = False
            if key.startswith('+'):
                selected = True
                key = key[1:]
            if key.startswith('*'):
                correct = True
                key = key[1:]
                correct_count += 1
            elif key.startswith('?'):
                correct = "neutral"
                key = key[1:]
            if key.endswith('.'):
                key = key[:-1]

            # Add YAML configuration data.
            optdata = {
                'value': key,
            }
            if correct:
                optdata['correct'] = correct
            if selected:
                optdata['selected'] = True

            # Create document elements.
            if dropdown is None:
                # One answer alternative as a radio button or a checkbox.
                choice = aplus_nodes.html('div', {'class': 'radio'})
                label = aplus_nodes.html('label', {})
                attrs = {
                    'type': self.input_type(),
                    'name': 'field_{:d}'.format(env.question_count - 1),
                    'value': key,
                }
                if selected:
                    attrs['checked'] = 'checked'
                label.append(aplus_nodes.html('input', attrs))
                choice.append(label)
                node.append(choice)

                text = aplus_nodes.html('span', {})
                text.store_html('label')
                nested_parse_with_titles(self.state, line, text)
                label.append(text)

                optdata['label'] = ('#!html', 'label')
                choice.set_yaml(optdata, 'option')
            else:
                # Add option elements to the select.
                # Options may only contain plain text, not formatted HTML.
                attrs = {
                    'value': key,
                }
                if selected:
                    attrs['selected'] = 'selected'
                option = aplus_nodes.html('option', attrs)
                text = line[0]
                option.append(nodes.Text(text))
                dropdown.append(option)

                optdata['label'] = html.escape(text)
                option.set_yaml(optdata, 'option')

        if dropdown is not None:
            node.append(dropdown)

        if 'randomized' in self.options:
            data['randomized'] = self.options.get('randomized', 1)
            if data['randomized'] > len(choices):
                source, line = self.state_machine.get_source_and_line(
                    self.lineno)
                raise SphinxError(
                    source + ": line " + str(line) +
                    "\nThe option 'randomized' can not be greater than the number of answer choices!"
                )
            if 'correct-count' in self.options:
                data['correct_count'] = self.options.get('correct-count', 0)
                if data['correct_count'] > correct_count or data[
                        'correct_count'] > data['randomized']:
                    source, line = self.state_machine.get_source_and_line(
                        self.lineno)
                    raise SphinxError(
                        source + ": line " + str(line) +
                        "\nThe option 'correct-count' can not be greater than "
                        "the number of correct choices or the value of 'randomized'!"
                    )
            if 'preserve-questions-between-attempts' in self.options:
                data['resample_after_attempt'] = False
            env.aplus_random_question_exists = True

        if 'checkbox-feedback' in self.options:
            data['checkbox_feedback'] = True

        self.add_feedback(node, data, feedback)

        return [node]
コード例 #25
0
    def run(self):
        self.length = self.options.get('length', None)
        self.height = self.options.get('height', 1)
        self.position = u'place-on-own-line' if self.height > 1 or u'own-line' in self.options else u'place-inline'

        # Detect paragraphs: any number of plain content and correct answer including optional feedback
        plain_content = None
        config_content = []
        env = self.state.document.settings.env
        if env.questionnaire_is_feedback:
            plain_content = self.content
        else:
            empty_lines = list(loc for loc,line in enumerate(self.content) if line == u"")
            if len(empty_lines) > 0:
                plain_content = self.content[:empty_lines[-1]]
                config_content = self.content[(empty_lines[-1] + 1):]
            else:
                config_content = self.content

        # Create question.
        env, node, data = self.create_question()
        self.add_instructions(node, data, plain_content)

        # Create input element.
        if self.height > 1:
            attrs = {
                u'rows': self.height,
                u'class': self.position,
            }
            if self.length:
                attrs[u'cols'] = self.length
            element = aplus_nodes.html(u'textarea', attrs)
        else:
            attrs = {
                u'type': u'text',
                u'class': self.position,
            }
            if self.length:
                attrs[u'size'] = self.length
            element = aplus_nodes.html(u'input', attrs)
        node.append(element)

        # Add configuration.
        if len(self.arguments) > 1:
            self._validate_compare_method(self.arguments[1])
            data[u'compare_method'] = self.arguments[1]

        if config_content:
            fb = None
            if u'§' in config_content[0]:
                correct,fb = config_content[0].split(u'§', 1)
            else:
                correct = config_content[0]
                config_content = config_content[1:]
            if u'°=°' in correct:
                correct,model = correct.split(u'°=°', 1)
                data[u'model'] = model.strip().replace(u"°°°", u"\n")
                if fb:
                    config_content[0] = correct.strip() + u" § " + fb.strip()
            data[u'correct'] = correct.strip().replace(u"°°°", u"\n")
            self.add_feedback(node, data, config_content)

        return [node]
コード例 #26
0
    def run(self):
        self.assert_has_content()

        # Detect paragraphs: any number of plain content, choices and optional feedback.
        empty_lines = list(loc for loc, line in enumerate(self.content)
                           if line == u'')
        plain_content = None
        choices = []
        feedback = []
        if len(empty_lines) > 0:
            last = self.content[(empty_lines[-1] + 1):]

            def split_second_last(empty_lines):
                if len(empty_lines) > 1:
                    return self.content[:empty_lines[-2]], self.content[(
                        empty_lines[-2] + 1):empty_lines[-1]]
                else:
                    return None, self.content[:empty_lines[-1]]

            # Backwards compatibility for skipping feedback paragraph.
            if len(last) == 1 and last[0].startswith(
                    u'I hereby declare that no feedback '):
                plain_content, choices = split_second_last(empty_lines)
            elif all(u'§' in line for line in last):
                plain_content, choices = split_second_last(empty_lines)
                feedback = last
            else:
                plain_content = self.content[:empty_lines[-1]]
                choices = last
        else:
            choices = self.content

        # Create question.
        env, node, data = self.create_question()
        self.add_instructions(node, data, plain_content)
        data[u'options'] = (u'#!children', u'option')

        dropdown = None
        if self.grader_field_type() == 'dropdown':
            # The HTML select element has a different structure compared
            # to the input elements (radio buttons and checkboxes).
            dropdown = aplus_nodes.html(
                'select', {
                    'name': 'field_{:d}'.format(env.question_count - 1),
                })

        # Travel all answer options.
        for i, line in slicer(choices):

            # Split choice key off.
            key, content = line[0].split(u' ', 1)
            key = key.strip()
            line[0] = content.strip()

            # Trim the key.
            correct = False
            selected = False
            if key.startswith('+'):
                selected = True
                key = key[1:]
            if key.startswith(u'*'):
                correct = True
                key = key[1:]
            if key.endswith(u'.'):
                key = key[:-1]

            # Add YAML configuration data.
            optdata = {
                'value': key,
            }
            if correct:
                optdata['correct'] = True
            if selected:
                optdata['selected'] = True

            # Create document elements.
            if dropdown is None:
                # One answer alternative as a radio button or a checkbox.
                choice = aplus_nodes.html('div', {'class': 'radio'})
                label = aplus_nodes.html('label', {})
                attrs = {
                    'type': self.input_type(),
                    'name': 'field_{:d}'.format(env.question_count - 1),
                    'value': key,
                }
                if selected:
                    attrs['checked'] = 'checked'
                label.append(aplus_nodes.html('input', attrs))
                choice.append(label)
                node.append(choice)

                text = aplus_nodes.html('span', {})
                text.store_html('label')
                nested_parse_with_titles(self.state, line, text)
                label.append(text)

                optdata['label'] = ('#!html', 'label')
                choice.set_yaml(optdata, 'option')
            else:
                # Add option elements to the select.
                # Options may only contain plain text, not formatted HTML.
                attrs = {
                    'value': key,
                }
                if selected:
                    attrs['selected'] = 'selected'
                option = aplus_nodes.html('option', attrs)
                text = line[0]
                option.append(nodes.Text(text))
                dropdown.append(option)

                optdata['label'] = html.escape(text)
                option.set_yaml(optdata, 'option')

        if dropdown is not None:
            node.append(dropdown)

        self.add_feedback(node, data, feedback)

        return [node]
コード例 #27
0
    def run(self):
        self.assert_has_content()
        if len(self.arguments) == 0:
            return [nodes.container()]
        name = self.arguments[0]
        title_text = ''
        if 'title' in  self.options:
            title_text = self.options['title']

        node = nodes.container()
        title = nodes.container()
        content = nodes.container('\n'.join(self.content))
        nav = nodes.container()
        links = nodes.container()
        # poi_nav node is later replaced by reference nodes which
        # need to be within a TextElement-node
        text = nodes.inline()
        poinav = poi_nav()
        # poinav id needs to be added so that we can identify the node later when processing
        poinav['ids'].append(name)

        container_class = 'poi-container'
        node['classes'].extend(['poi'])
        if 'class' in self.options:
            node['classes'].extend(self.options['class'])
        title['classes'].extend(['poi-title'])
        links['classes'].extend(['poi-links'])
        content['classes'].extend([container_class, 'poi-content', 'collapse'])
        if not 'hidden' in self.options:
            content['classes'].extend(['in'])
        nav['classes'].extend([container_class])

        self.options['name'] = name
        self.add_name(node)
        content_name = name + '-content'
        content['ids'].append(content_name)

        icon = aplus_nodes.html(u'img', {
            u'src':u'../_static/poi.png',
            u'alt':u'Point of interest icon',
            u'class':u'poi-icon',
            })
        hidelink = aplus_nodes.html(u'a', {
            u'href':u'#' + content_name,
            u'data-toggle':u'collapse'})

        hidelink.append(icon)
        hidelink.append(nodes.Text(title_text))
        title.append(hidelink)
        nav.append(title)
        text.append(poinav)
        links.append(text)
        nav.append(links)

        node.append(nav)
        node.append(content)

        self.state.nested_parse(self.content, self.content_offset, content)

        # poi_info needs to be stored to env to be able to construct the
        # refuris later
        env = self.state.document.settings.env
        poi_info = {
            'docname': env.docname,
        }
        if 'previous' in self.options:
            poi_info['previous'] = self.options['previous']
        if 'next' in self.options:
            poi_info['next'] = self.options['next']

        if not hasattr(env, 'poi_all'):
            env.poi_all = {}
        env.poi_all[name] = poi_info

        return [node]
コード例 #28
0
    def run(self):
        self.assert_has_content()
        key, difficulty, points = self.extract_exercise_arguments()

        # Parse options.
        classes = [u'exercise']
        is_feedback = False
        if 'chapter-feedback' in self.options:
            classes.append(u'chapter-feedback')
            is_feedback = True
        if 'weekly-feedback' in self.options:
            classes.append(u'weekly-feedback')
            is_feedback = True
        if 'appendix-feedback' in self.options:
            classes.append(u'appendix-feedback')
            is_feedback = True
        if 'course-feedback' in self.options:
            classes.append(u'course-feedback-questionnaire')
            is_feedback = True
        if 'feedback' in self.options:
            is_feedback = True
        if is_feedback:
            key = u'feedback'
            category = u'feedback'
            classes.append(u'feedback')
        else:
            category = u'questionnaire'
            if difficulty:
                classes.append(u'difficulty-' + difficulty)

        if 'category' in self.options:
            category = str(self.options.get('category'))

        env = self.state.document.settings.env
        name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key)
        override = env.config.override

        env.questionnaire_is_feedback = is_feedback
        env.question_count = 0

        # Create document elements.
        node = aplus_nodes.html(u'div', {
            u'class': u' '.join(classes),
            u'data-aplus-exercise': u'yes',
            u'data-aplus-quiz': u'yes',
        })
        form = aplus_nodes.html(u'form', {
            u'action': key,
            u'method': u'post',
        })
        nested_parse_with_titles(self.state, self.content, form)

        submit = aplus_nodes.html(u'input', {
            u'type': u'submit',
            u'value': translations.get(env, u'submit'),
            u'class': u'btn btn-primary',
        }, skip_html=True)
        form.append(submit)
        node.append(form)

        # Write configuration file.
        data = {
            u'key': name,
            u'category': category,
            u'max_points': points,
            u'difficulty': difficulty or '',
            u'max_submissions': self.options.get('submissions', 0 if is_feedback else env.config.questionnaire_default_submissions),
            u'min_group_size': 1 if is_feedback else env.config.default_min_group_size,
            u'max_group_size': 1 if is_feedback else env.config.default_max_group_size,
            u'points_to_pass': self.options.get('points-to-pass', 0),
            u'feedback': is_feedback,
            u'view_type': u'access.types.stdsync.createForm',
            u'title|i18n': translations.opt('feedback') if is_feedback else translations.opt('exercise', postfix=u" {}".format(key)),
            u'fieldgroups': [{
                u'title': '',
                u'fields': (u'#!children', None),
            }],
        }
        if not 'no-override' in self.options and category in override:
            data.update(override[category])
            if 'url' in data:
                data['url'] = data['url'].format(key=name)
        if "pick_randomly" in self.options:
            pick_randomly = self.options.get('pick_randomly', 0)
            if pick_randomly < 1:
                raise SphinxError(u'Number of fields to sample randomly should greater than zero.')
            data[u'fieldgroups'][0]['pick_randomly'] = pick_randomly

        form.write_yaml(env, name, data, 'exercise')

        return [node]
コード例 #29
0
    def run(self):
        self.length = self.options.get('length', None)
        self.height = self.options.get('height', 1)
        self.position = u'place-on-own-line' if self.height > 1 or u'own-line' in self.options else u'place-inline'

        # Detect paragraphs: any number of plain content and correct answer including optional feedback
        plain_content = None
        config_content = []
        env = self.state.document.settings.env
        if env.questionnaire_is_feedback:
            plain_content = self.content
        else:
            empty_lines = list(loc for loc, line in enumerate(self.content)
                               if line == u"")
            if len(empty_lines) > 0:
                plain_content = self.content[:empty_lines[-1]]
                config_content = self.content[(empty_lines[-1] + 1):]
            else:
                config_content = self.content

        # Create question.
        env, node, data = self.create_question()
        self.add_instructions(node, data, plain_content)

        # Create input element.
        if self.height > 1:
            attrs = {
                u'rows': self.height,
                u'class': self.position,
            }
            if self.length:
                attrs[u'cols'] = self.length
            element = aplus_nodes.html(u'textarea', attrs)
        else:
            attrs = {
                u'type': u'text',
                u'class': self.position,
            }
            if self.length:
                attrs[u'size'] = self.length
            element = aplus_nodes.html(u'input', attrs)
        node.append(element)

        # Add configuration.
        if len(self.arguments) > 1:
            self._validate_compare_method(self.arguments[1])
            data[u'compare_method'] = self.arguments[1]

        if config_content:
            fb = None
            if u'§' in config_content[0]:
                correct, fb = config_content[0].split(u'§', 1)
            else:
                correct = config_content[0]
                config_content = config_content[1:]
            if u'°=°' in correct:
                correct, model = correct.split(u'°=°', 1)
                data[u'model'] = model.strip().replace(u"°°°", u"\n")
                if fb:
                    config_content[0] = correct.strip() + u" § " + fb.strip()
            data[u'correct'] = correct.strip().replace(u"°°°", u"\n")
            self.add_feedback(node, data, config_content)

        return [node]
コード例 #30
0
    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]
コード例 #31
0
    def run(self):

        if len(self.arguments) > 0:
            key = self.arguments[0]
        else:
            raise SphinxError('Missing active element input id')

        env = self.state.document.settings.env

        name = "{}_{}".format(env.docname.replace('/', '_'), key)
        override = env.config.override

        classes = []
        if 'class' in self.options:
            classes.extend(self.options['class'])

        # Add document nodes.
        args = {
            'class': ' '.join(classes),
            'data-aplus-active-element': 'in',
            'id': '' + key,
        }

        if 'title' in self.options:
            args['data-title'] = self.options['title']

        if 'default' in self.options:
            args['data-default'] = self.options['default']

        if 'type' in self.options:
            args['data-type'] = self.options['type']

        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('div', args)
        paragraph = aplus_nodes.html('p', {})
        paragraph.append(
            nodes.Text(translations.get(env, 'active_element_placeholder')))
        node.append(paragraph)

        # For clickable inputs the pre-generated html
        # needs to be added after the regular a+ exercise node
        if 'type' in self.options and self.options['type'] == 'clickable':
            if 'file' not in self.options:
                logger.warning(
                    'Clickable active element input "{}" missing template file.'
                    .format(name),
                    location=node)
                return [node]

            # Read given html file (from docutils.directives.misc.raw)
            source_dir = os.path.dirname(
                os.path.abspath(self.state.document.current_source))
            path = os.path.join(env.app.srcdir, self.options['file'])
            path = utils.relative_path(None, path)
            try:
                raw_file = io.FileInput(
                    source_path=path,
                    encoding=self.state.document.settings.input_encoding,
                    error_handler=self.state.document.settings.
                    input_encoding_error_handler)
            except IOError as error:
                logger.error('Problem with "%s" directive:\n%s.' %
                             (self.name, ErrorString(error)),
                             location=node)
                return []
            try:
                text = raw_file.read()
            except UnicodeError as error:
                logger.error('Problem with "%s" directive:\n%s.' %
                             (self.name, ErrorString(error)),
                             location=node)
                return []

            # Generate raw node
            rawnode = nodes.raw('', text, **{'format': 'html'})
            wrapnode = aplus_nodes.html('div', {
                'id': '' + key + '-wrap',
                'class': 'clickable-ae-wrapper'
            })
            wrapnode.append(node)
            wrapnode.append(rawnode)
            return [wrapnode]

        return [node]
コード例 #32
0
    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]
コード例 #33
0
    def run(self):
        self.assert_has_content()
        key, difficulty, points = self.extract_exercise_arguments()

        # Parse options.
        classes = [u'exercise']
        is_feedback = False
        if 'chapter-feedback' in self.options:
            classes.append(u'chapter-feedback')
            is_feedback = True
        if 'weekly-feedback' in self.options:
            classes.append(u'weekly-feedback')
            is_feedback = True
        if 'appendix-feedback' in self.options:
            classes.append(u'appendix-feedback')
            is_feedback = True
        if 'course-feedback' in self.options:
            classes.append(u'course-feedback-questionnaire')
            is_feedback = True
        if 'feedback' in self.options:
            is_feedback = True
        if is_feedback:
            key = u'feedback'
            category = u'feedback'
            classes.append(u'feedback')
        else:
            category = u'questionnaire'
            if difficulty:
                classes.append(u'difficulty-' + difficulty)

        env = self.state.document.settings.env
        name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key)
        override = env.config.override

        env.questionnaire_is_feedback = is_feedback
        env.question_count = 0

        # Create document elements.
        node = aplus_nodes.html(
            u'div', {
                u'class': u' '.join(classes),
                u'data-aplus-exercise': u'yes',
                u'data-aplus-quiz': u'yes',
            })
        form = aplus_nodes.html(u'form', {
            u'action': key,
            u'method': u'post',
        })
        nested_parse_with_titles(self.state, self.content, form)

        submit = aplus_nodes.html(u'input', {
            u'type': u'submit',
            u'value': translations.get(env, u'submit'),
            u'class': u'btn btn-primary',
        },
                                  skip_html=True)
        form.append(submit)
        node.append(form)

        # Write configuration file.
        data = {
            u'key':
            name,
            u'category':
            category,
            u'max_points':
            points,
            u'difficulty':
            difficulty or '',
            u'max_submissions':
            self.options.get(
                'submissions', 0 if is_feedback else
                env.config.questionnaire_default_submissions),
            u'min_group_size':
            1 if is_feedback else env.config.default_min_group_size,
            u'max_group_size':
            1 if is_feedback else env.config.default_max_group_size,
            u'points_to_pass':
            self.options.get('points-to-pass', 0),
            u'feedback':
            is_feedback,
            u'view_type':
            u'access.types.stdsync.createForm',
            u'title|i18n':
            translations.opt('feedback') if is_feedback else translations.opt(
                'exercise', postfix=u" {}".format(key)),
            u'fieldgroups': [{
                u'title': '',
                u'fields': (u'#!children', None),
            }],
        }
        if not 'no-override' in self.options and category in override:
            data.update(override[category])
            if 'url' in data:
                data['url'] = data['url'].format(key=name)
        if "pick_randomly" in self.options:
            pick_randomly = self.options.get('pick_randomly', 0)
            if pick_randomly < 1:
                raise SphinxError(
                    u'Number of fields to sample randomly should greater than zero.'
                )
            data[u'fieldgroups'][0]['pick_randomly'] = pick_randomly

        form.write_yaml(env, name, data, 'exercise')

        return [node]
コード例 #34
0
    def run(self):

        if len(self.arguments) > 0:
            key = self.arguments[0]
        else:
            raise SphinxError('Missing active element input id')

        env = self.state.document.settings.env

        name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key)
        override = env.config.override

        classes = []
        if 'class' in self.options:
            classes.extend(self.options['class'])

        # Add document nodes.
        args = {
            u'class': u' '.join(classes),
            u'data-aplus-active-element': u'in',
            u'id': u''+ key,
        }

        if 'title' in self.options:
            args['data-title'] = self.options['title']

        if 'default' in self.options:
            args['data-default'] = self.options['default']

        if 'type' in self.options:
            args['data-type'] = self.options['type']

        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, 'active_element_placeholder')))
        node.append(paragraph)

        # For clickable inputs the pre-generated html
        # needs to be added after the regular a+ exercise node
        if 'type' in self.options and self.options['type'] == 'clickable':
            if 'file' not in self.options:
                logger.warning('Clickable active element input "{}" missing template file.'
                                .format(name), location=node)
                return [node]

            # Read given html file (from docutils.directives.misc.raw)
            source_dir = os.path.dirname(
                os.path.abspath(self.state.document.current_source))
            path = os.path.join(env.app.srcdir, self.options['file'])
            path = utils.relative_path(None, path)
            try:
                raw_file = io.FileInput(source_path=path,
                                        encoding=self.state.document.settings.input_encoding,
                                        error_handler=self.state.document.settings.input_encoding_error_handler)
            except IOError as error:
                logger.error(u'Problem with "%s" directive:\n%s.'
                                  % (self.name, ErrorString(error)), location=node)
                return []
            try:
                text = raw_file.read()
            except UnicodeError as error:
                logger.error(u'Problem with "%s" directive:\n%s.'
                                  % (self.name, ErrorString(error)), location=node)
                return []

            # Generate raw node
            rawnode = nodes.raw('', text, **{'format':'html'})
            wrapnode = aplus_nodes.html(u'div', {u'id': u''+ key + '-wrap',u'class': u'clickable-ae-wrapper'})
            wrapnode.append(node)
            wrapnode.append(rawnode)
            return [wrapnode]

        return [node]
コード例 #35
0
    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]
コード例 #36
0
ファイル: submit.py プロジェクト: roosapi/a-plus-rst-tools
    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]
コード例 #37
0
    def run(self):
        self.assert_has_content()

        # Detect paragraphs: any number of plain content, choices and optional feedback.
        empty_lines = list(loc for loc,line in enumerate(self.content) if line == u'')
        plain_content = None
        choices = []
        feedback = []
        if len(empty_lines) > 0:
            last = self.content[(empty_lines[-1] + 1):]

            def split_second_last(empty_lines):
                if len(empty_lines) > 1:
                    return self.content[:empty_lines[-2]], self.content[(empty_lines[-2] + 1):empty_lines[-1]]
                else:
                    return None, self.content[:empty_lines[-1]]

            # Backwards compatibility for skipping feedback paragraph.
            if len(last) == 1 and last[0].startswith(u'I hereby declare that no feedback '):
                plain_content, choices = split_second_last(empty_lines)
            elif all(u'§' in line for line in last):
                plain_content, choices = split_second_last(empty_lines)
                feedback = last
            else:
                plain_content = self.content[:empty_lines[-1]]
                choices = last
        else:
            choices = self.content

        # Create question.
        env, node, data = self.create_question()
        self.add_instructions(node, data, plain_content)
        data[u'options'] = (u'#!children', u'option')

        # Travel all answer options.
        for i,line in slicer(choices):

            # Split choice key off.
            key,content = line[0].split(u' ', 1)
            key = key.strip()
            line[0] = content.strip()

            # Trim the key.
            correct = False
            if key.startswith(u'*'):
                correct = True
                key = key[1:]
            if key.endswith(u'.'):
                key = key[:-1]

            # Create document elements.
            choice = aplus_nodes.html(u'div', {u'class':u'radio'})
            label = aplus_nodes.html(u'label', {})
            label.append(aplus_nodes.html(u'input', {
                u'type': self.input_type(),
                u'name': u'field_{:d}'.format(env.question_count - 1),
                u'value': key,
            }))
            choice.append(label)
            node.append(choice)

            text = aplus_nodes.html(u'span', {})
            text.store_html(u'label')
            nested_parse_with_titles(self.state, line, text)
            label.append(text)

            # Add configuration data.
            optdata = {
                u'value': key,
                u'label': (u'#!html', u'label'),
            }
            if correct:
                optdata[u'correct'] = True
            choice.set_yaml(optdata, u'option')

        self.add_feedback(node, data, feedback)

        return [node]
コード例 #38
0
    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]
コード例 #39
0
    def run(self):
        self.assert_has_content()
        key, difficulty, points = self.extract_exercise_arguments()

        # Parse options.
        classes = [u'exercise']
        is_feedback = False
        if 'chapter-feedback' in self.options:
            classes.append(u'chapter-feedback')
            is_feedback = True
        if 'weekly-feedback' in self.options:
            classes.append(u'weekly-feedback')
            is_feedback = True
        if 'appendix-feedback' in self.options:
            classes.append(u'appendix-feedback')
            is_feedback = True
        if 'course-feedback' in self.options:
            classes.append(u'course-feedback-questionnaire')
            is_feedback = True
        if 'feedback' in self.options:
            is_feedback = True
        if is_feedback:
            key = u'feedback'
            category = u'feedback'
            classes.append(u'feedback')
        else:
            category = u'questionnaire'
            if difficulty:
                classes.append(u'difficulty-' + difficulty)

        if 'category' in self.options:
            category = str(self.options.get('category'))

        env = self.state.document.settings.env
        name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key)
        override = env.config.override

        env.questionnaire_is_feedback = is_feedback
        env.question_count = 0
        env.aplus_single_question_points = None
        env.aplus_quiz_total_points = 0
        env.aplus_pick_randomly_quiz = 'pick_randomly' in self.options

        # Create document elements.
        node = aplus_nodes.html(
            u'div', {
                u'class': u' '.join(classes),
                u'data-aplus-exercise': u'yes',
                u'data-aplus-quiz': u'yes',
            })
        form = aplus_nodes.html(u'form', {
            u'action': key,
            u'method': u'post',
        })
        nested_parse_with_titles(self.state, self.content, form)

        submit = aplus_nodes.html(u'input', {
            u'type': u'submit',
            u'value': translations.get(env, u'submit'),
            u'class': u'btn btn-primary',
        },
                                  skip_html=True)
        form.append(submit)
        node.append(form)

        # Write configuration file.
        data = {
            u'key':
            name,
            u'category':
            category,
            u'difficulty':
            difficulty or '',
            u'max_submissions':
            self.options.get(
                'submissions', 0 if is_feedback else
                env.config.questionnaire_default_submissions),
            u'min_group_size':
            1 if is_feedback else env.config.default_min_group_size,
            u'max_group_size':
            1 if is_feedback else env.config.default_max_group_size,
            u'points_to_pass':
            self.options.get('points-to-pass', 0),
            u'feedback':
            is_feedback,
            u'view_type':
            u'access.types.stdsync.createForm',
            u'status':
            self.options.get('status', 'unlisted'),
            u'fieldgroups': [{
                u'title': '',
                u'fields': (u'#!children', None),
            }],
        }
        self.set_assistant_permissions(data)

        points_set_in_arguments = len(
            self.arguments) == 2 and difficulty != self.arguments[1]

        if 'pick_randomly' in self.options:
            calculated_max_points = (self.options.get('pick_randomly') *
                                     env.aplus_single_question_points
                                     if env.aplus_single_question_points
                                     is not None else 0)
        else:
            calculated_max_points = env.aplus_quiz_total_points

        if calculated_max_points == 0 and is_feedback:
            data['max_points'] = points
        else:
            if points_set_in_arguments and calculated_max_points != points:
                source, line = self.state_machine.get_source_and_line(
                    self.lineno)
                raise SphinxError(
                    source + ": line " + str(line) +
                    "\nThe points of the questions in the questionnaire must add up to the total points of the questionnaire!"
                )
            data['max_points'] = calculated_max_points

        if 'title' in self.options:
            data['title'] = self.options.get('title')
        else:
            data[u'title|i18n'] = translations.opt(
                'feedback') if is_feedback else translations.opt(
                    'exercise', postfix=u" {}".format(key))

        if not 'no-override' in self.options and category in override:
            data.update(override[category])
            if 'url' in data:
                data['url'] = data['url'].format(key=name)
        if "pick_randomly" in self.options:
            pick_randomly = self.options.get('pick_randomly', 0)
            if pick_randomly < 1:
                raise SphinxError(
                    u'Number of fields to sample randomly should greater than zero.'
                )
            data[u'fieldgroups'][0]['pick_randomly'] = pick_randomly

        form.write_yaml(env, name, data, 'exercise')

        return [node]
コード例 #40
0
    def run(self):
        self.assert_has_content()

        # Detect paragraphs: any number of plain content, choices and optional feedback.
        empty_lines = list(loc for loc,line in enumerate(self.content) if line == u'')
        plain_content = None
        choices = []
        feedback = []
        if len(empty_lines) > 0:
            last = self.content[(empty_lines[-1] + 1):]

            def split_second_last(empty_lines):
                if len(empty_lines) > 1:
                    return self.content[:empty_lines[-2]], self.content[(empty_lines[-2] + 1):empty_lines[-1]]
                else:
                    return None, self.content[:empty_lines[-1]]

            # Backwards compatibility for skipping feedback paragraph.
            if len(last) == 1 and last[0].startswith(u'I hereby declare that no feedback '):
                plain_content, choices = split_second_last(empty_lines)
            elif all(u'§' in line for line in last):
                plain_content, choices = split_second_last(empty_lines)
                feedback = last
            else:
                plain_content = self.content[:empty_lines[-1]]
                choices = last
        else:
            choices = self.content

        # Create question.
        env, node, data = self.create_question()
        self.add_instructions(node, data, plain_content)
        data[u'options'] = (u'#!children', u'option')

        # Travel all answer options.
        for i,line in slicer(choices):

            # Split choice key off.
            key,content = line[0].split(u' ', 1)
            key = key.strip()
            line[0] = content.strip()

            # Trim the key.
            correct = False
            if key.startswith(u'*'):
                correct = True
                key = key[1:]
            if key.endswith(u'.'):
                key = key[:-1]

            # Create document elements.
            choice = aplus_nodes.html(u'div', {u'class':u'radio'})
            label = aplus_nodes.html(u'label', {})
            label.append(aplus_nodes.html(u'input', {
                u'type': self.input_type(),
                u'name': u'field_{:d}'.format(env.question_count - 1),
                u'value': key,
            }))
            choice.append(label)
            node.append(choice)

            text = aplus_nodes.html(u'span', {})
            text.store_html(u'label')
            nested_parse_with_titles(self.state, line, text)
            label.append(text)

            # Add configuration data.
            optdata = {
                u'value': key,
                u'label': (u'#!html', u'label'),
            }
            if correct:
                optdata[u'correct'] = True
            choice.set_yaml(optdata, u'option')

        self.add_feedback(node, data, feedback)

        return [node]
コード例 #41
0
    def run(self):
        self.assert_has_content()
        if len(self.arguments) == 0:
            return [nodes.container()]
        name = self.arguments[0]
        title_text = ''
        if 'title' in self.options:
            title_text = self.options['title']

        node = nodes.container()
        title = nodes.container()
        content = nodes.container('\n'.join(self.content))
        nav = nodes.container()
        links = nodes.container()
        # poi_nav node is later replaced by reference nodes which
        # need to be within a TextElement-node
        text = nodes.inline()
        poinav = poi_nav()
        # poinav id needs to be added so that we can identify the node later when processing
        poinav['ids'].append(name)

        container_class = 'poi-container'
        node['classes'].extend(['poi'])
        if 'class' in self.options:
            node['classes'].extend(self.options['class'])
        title['classes'].extend(['poi-title'])
        links['classes'].extend(['poi-links'])
        content['classes'].extend([container_class, 'poi-content', 'collapse'])
        if not 'hidden' in self.options:
            content['classes'].extend(['in'])
        nav['classes'].extend([container_class])

        self.options['name'] = name
        self.add_name(node)
        content_name = name + '-content'
        content['ids'].append(content_name)

        icon = aplus_nodes.html(
            u'img', {
                u'src': u'../_static/poi.png',
                u'alt': u'Point of interest icon',
                u'class': u'poi-icon',
            })
        hidelink = aplus_nodes.html(u'a', {
            u'href': u'#' + content_name,
            u'data-toggle': u'collapse'
        })

        hidelink.append(icon)
        hidelink.append(nodes.Text(title_text))
        title.append(hidelink)
        nav.append(title)
        text.append(poinav)
        links.append(text)
        nav.append(links)

        node.append(nav)
        node.append(content)

        self.state.nested_parse(self.content, self.content_offset, content)

        # poi_info needs to be stored to env to be able to construct the
        # refuris later
        env = self.state.document.settings.env
        poi_info = {
            'docname': env.docname,
        }
        if 'previous' in self.options:
            poi_info['previous'] = self.options['previous']
        if 'next' in self.options:
            poi_info['next'] = self.options['next']

        if not hasattr(env, 'poi_all'):
            env.poi_all = {}
        env.poi_all[name] = poi_info

        return [node]
コード例 #42
0
    def run(self):
        self.assert_has_content()
        env = self.state.document.settings.env
        if not hasattr(env, 'poi_all'):
            env.poi_all = {}
        if len(self.arguments) == 0:
            return [nodes.container()]

        if 'title' in self.options:
            if 'id' in self.options:
                raise SphinxError(u'Point of interest options can\'t contain both "title" and "id"; one of them should be provided as an argument instead')
            name = self.arguments[0]
            title_text = self.options['title']
        else:
            name = self.options.get('id')
            if not name:
                choices = string.ascii_lowercase + string.digits
                while True:
                    name = ''.join(random.choice(choices) for i in range(6))
                    if name not in env.poi_all:
                        break
            title_text = self.arguments[0]

        container_class = 'poi-container'
        content_name = name + '-content'

        node = nodes.container()
        title = nodes.container()

        # add an extra div to force content to desired height
        hcontainer_opts = {
            u'style':'height:'+self.options.get('height', '')+';',
            u'class': container_class +' poi-content row',
        }
        if 'bgimg' in self.options:
            static_host = os.environ.get('STATIC_CONTENT_HOST', None)
            if not static_host:
                logger.warning('Environment variable STATIC_CONTENT_HOST must be set to be able to use point of interest background', location=node)
            else:
                docname = env.docname
                imgname = self.options['bgimg'].split('/')[-1]
                imgpath = ('/').join(docname.split('/')[0:-1]) + '/' + self.options['bgimg']
                urlstring = 'background-image:url(' + static_host + '/_images/' + imgname + ');'
                hcontainer_opts['style'] = hcontainer_opts['style'] + urlstring
                # Add background image to env and builder so that it will
                # be correctly copied to static _images build directory
                env.images[imgpath] = ({docname}, imgname)
                env.app.builder.images[imgpath] = imgname

        hcontainer = aplus_nodes.html(u'div', hcontainer_opts)
        collapsible = nodes.container()

        contentnodes = []
        colseparator = '::newcol'
        colcontent = ''
        colcontent_l = []
        colwidths = None
        prevl = 0
        l = 0
        if 'columns' in self.options:
            cols = list(map(int,self.options['columns'].split()))
            allcols = sum(cols)
            colwidths = [x / allcols for x in cols]


        for batch in self.content:
            l += 1
            if batch == colseparator:
                cn = len(contentnodes)
                cwidth = None
                if colwidths:
                    cwidth = floor(colwidths[cn] * 12)
                cnode = nodes.container(colcontent)
                hcontainer.append(cnode)
                contentnodes.append((self.content[prevl:l-1], self.content_offset + prevl, cnode, cwidth))

                colcontent = ''
                prevl = l
            else:
                colcontent_l.append(batch)
                colcontent += batch
                colcontent += '\n'

        # add last column
        cnode = nodes.container(colcontent)
        hcontainer.append(cnode)
        cwidth = None
        if colwidths:
            cwidth = floor(colwidths[-1] * 12)
        contentnodes.append((self.content[prevl:], self.content_offset + prevl, cnode, cwidth))

        nav = nodes.container()
        links = nodes.container()
        # poi_nav node is later replaced by reference nodes which
        # need to be within a TextElement-node
        text = nodes.inline()
        poinav = poi_nav()
        icon = aplus_nodes.html(u'img', {
            u'src':u'../_static/poi.png',
            u'alt':u'Point of interest icon',
            u'class':u'poi-icon',
            })
        hidelink = aplus_nodes.html(u'a', {
            u'href':u'#' + content_name,
            u'data-toggle':u'collapse'})

        hidelink.append(icon)
        hidelink.append(nodes.Text(title_text))
        title.append(hidelink)
        nav.append(title)
        text.append(poinav)
        links.append(text)
        nav.append(links)

        node.append(nav)
        node.append(collapsible)
        collapsible.append(hcontainer)

        numcols = len(contentnodes)
        for cont, offset, cnode, cwidth in contentnodes:
            # Bootstrap uses 12 unit grid
            if not cwidth:
                cwidth = int(12/numcols)
            cnode['classes'] = ['col-sm-' + str(cwidth)]
            self.state.nested_parse(cont, offset, cnode)

        # poinav id needs to be added so that we can identify the node later when processing
        poinav['ids'].append(name)

        self.options['name'] = name
        self.add_name(node)
        collapsible['ids'].append(content_name)
        collapsible['classes'].extend([container_class, 'collapse'])

        node['classes'].extend(['poi'])
        if 'class' in self.options:
            node['classes'].extend(self.options['class'])
        title['classes'].extend(['poi-title'])
        links['classes'].extend(['poi-links'])

        if not 'hidden' in self.options:
            collapsible['classes'].extend(['in'])
        nav['classes'].extend([container_class])

        # poi_info needs to be stored to env to be able to construct the
        # refuris later
        poi_info = {
            'docname': env.docname,
        }
        if 'previous' in self.options:
            poi_info['previous'] = self.options['previous']
        if 'next' in self.options:
            poi_info['next'] = self.options['next']

        env.poi_all[name] = poi_info
        return [node]