def __init__(self, stem=None, choices=None, feedback=None, comment=None): super(multiplechoice, self).__init__() self.children.append(Join.as_element(stem or [])) self.children.append(_choices_cls(choices or [])) self.children.append(feedback or Join([])) self.children.append(comment or Join([])) self.id = id(self)
def process(path): '''Process a TeX document file''' # Read and process full TeX file with open(path, encoding='utf8') as F: tex = TeX(F.read(), context=context) # Remove title/author/etc data if strip_meta: elements = tex.document.clear() preamble, _, elements = partition_type(elements, latex.maketitle) return Join(elements or preamble) else: return Join(tex.document.clear())
def revalue_alttex(self, section, idx, **kwds): '''Picks up the correct option''' # Compute a predictable shuffling from the idx argument stripped_src = self.options.source() stripped_src = ''.join(stripped_src.split()) random.seed(stripped_src + str(idx)) options = self.options.revalue('alttex', section, idx, **kwds) L = options.children.clear() random.shuffle(L) # Insert elements in shufle points and return body body = self.body.revalue('alttex', section, idx, **kwds) for spoint in walk_items(body, shufflepoint): spoint.replace_by(L.pop()) # Create return element new = Join(self.body.children.clear()) return new if len(new) != 1 else new[0]
def render_table(self, rows=None, cols=None, idx=None, section=None): '''Render choices in a table''' if rows is None and cols is None: raise ValueError('at least cols or rows must be specified') # Define shape for the table N = len(self) if cols is None: cols = math.ceil(N / rows) else: rows = math.ceil(N / cols) final = latex.tabular.empty(rows, cols, lines=False) # @UndefinedVariable cell_choice = zip(final.iter_cells(transpose=True), self.user_choices()) for idx, (cell, choice) in enumerate(cell_choice): label = Text('(%s) ' % letter(idx)) cell.replace_by(Join([label] + choice.clear())) final.set_alignment('left') return final
def invoke_body(cls, job, tokens, new): data = super(multiplechoice, cls).invoke_body(job, tokens, new) data = Join(data) # Get comments and feedback new.feedback = Join.as_element([ x.unlinked() for x in data if isinstance(x, feedback) ]) new.comment = Join.as_element([ x.unlinked() for x in data if isinstance(x, comment) ]) # Discard all data after choices try: choices_ = data.get(choices) [ x.unlink() for x in choices_.get_siblings_next() ] except ValueError: choices_ = choices() # Reorganize elements new.choices = choices_.unlinked() new.stem = Join.as_element(strip(data.clear())) new.id = id(new) return []
def user_choices(self, idx=None, section=None): r'''Iterates over the user-visible data for each choice, discarding all commands such as \correct, \feedback, etc''' for choice in self: try: choice.get(correct) is_correct = True except ValueError: is_correct = False choice = choice.copy() choice.remove_all(correct) choice.remove_all(feedback) choice.remove_all(comment) choice = Join(choice.clear()) if is_correct: ifdata = choice.copy().revalue('alttex', idx, 'answers') ifdata = r'\textcolor{red}{\textbf{<<}~ %s ~\textbf{>>}}' % ifdata.source() ifdata = TeXString(ifdata) choice = Join([altcond(['answers'], [ifdata], [choice.copy()])]) yield choice
def revalue_template(self, idx, **kwds): for i, child in enumerate(self.children): new = child.revalue('template', idx, **kwds) if new is not child: self.children[i] = new stem, choices, feedback, comment = self.clear() opts = dict(self.options or {}) final = Join() final.add(stem) final.add(TeXString('\n%\n\\par\\smallskip\n%\n')) # See if choices needs to be populated from vars if opts.get('varchoices') is not None: varchoices = opts['varchoices'] varchoices = '0' if varchoices is True else varchoices if varchoices.isdigit(): varchoices = 'choices' + varchoices # Get choices object choices = var.get_variable(self, varchoices) # Apply options in choices object if opts.get('shuffle'): del opts['shuffle'] choices.shuffle() if opts.get('sort'): del opts['sort'] choices.sort() choices = choices.texfy() # Shuffle choices, if required if opts.get('shuffle', False): data = choices.clear() seed = self.id + idx random.seed(seed) random.shuffle(data) choices.add(data) # Format choices depending on the arguments if opts.get('horizontal', False): final.add(choices.render_horizontal(idx=idx)) elif opts.get('rows', None) is not None: final.add(choices.render_table(rows=int(opts['rows']), idx=idx)) elif opts.get('cols', None) is not None: final.add(choices.render_table(cols=int(opts['cols']), idx=idx)) elif opts.get('vertical', True): choices = final.add(choices.render_vertical(idx=idx)) # Adds an extra spacing after choices final.add(TeXString('\n%\n\\endgraf\\smallskip{}\n%\n')) # Print feedback, comments, etc, depending on the section return Group('\\begingroup\n', final.clear(), '\n\\endgroup')
def render_horizontal(self, idx=None, section=None): '''Render choices horizontally''' final = Join() final.add(TeXString(' \quad ')) for idx, choice in enumerate(self.user_choices(idx=idx, section=section)): final.add(Text('(%s) ' % letter(idx))) final.add(choice.copy()) final.add(TeXString(' \qquad ')) final.pop() return final
def invoke_body(cls, job, tokens, new): '''Read the list of children and find the shuffle points and its possible values in the data stream''' options = [] body = Join(job.read_all(tokens)) while True: # we put this in a while loop in order to process only the first # occurrence of a string with a shuffle block opening # The workload is: # 1) find the first string matching with a '<<' # 2) extract the contents until a '>>' is found # 3) save it and the previous element into the options list # 4) repeat the operation with a new walk_items() search # 5) stop repeating when walk_items() finds no matching string # for obj in walk_items(body, str): # Search for the beginning of the shuffle block if '<<' in obj: pre, _, post = obj.partition('<<') obj.replace_by(pre) if post: pre.insert_next(post) # create a shuffle point spoint = shufflepoint() if pre: pre.insert_next(spoint) else: pre.replace_by(spoint) else: continue # Search for the closing tag for the shuffle block option = [] for obj in spoint.get_siblings_next(): if isinstance(obj, str) and '>>' in obj: data, _, post = obj.partition('>>') option.append(data) obj.replace_by(post) break else: obj.unlink() option.append(obj) else: raise RuntimeError('closing >> block not found!') if len(option) == 1: option = option[0] else: option = Join(option) options.append(option) # if the walk_items loop complete without finding any match for a # opening block, break the main while loop. else: break # We don't want to extend our children # Let us add elements to the existing options and body groups new.options.children.extend(options) new.body.children.extend(body.children.clear()) return []
def __init__(self, options=None, tex=None): super(shuffle, self).__init__() self.children.extend([options or Join(), tex or Join()])