示例#1
0
def render_gs_anchor(pdf: PDF, variant, score=0):
    '''
        Renders the gs anchor box for a given question/question part.
         -1: Empty box. Intended for template variants.
          0: Completely incorrect answer.
        < 1: Partially correct answer.
          1: Completely correct answer.
    '''
    if score == -1:
        a_cfg = get_cfg('gsAnchor', 'blank')
    elif score == 0:
        a_cfg = get_cfg('gsAnchor', 'incorrect')
    elif score < 1:
        a_cfg = get_cfg('gsAnchor', 'partial')
    elif score == 1:
        a_cfg = get_cfg('gsAnchor', 'correct')
    else:
        return
    fill = a_cfg['fill']
    text = f'{a_cfg["text"]}: {score}' if score > -1 else a_cfg['text']
    pdf.set_font(cfg['font']['body']['font'])
    pdf.set_fill_color(fill['r'], fill['g'], fill['b'])
    pdf.cell(lineWidth,
             h=get_cfg('gsAnchor', 'height'),
             txt=text,
             fill=True)
    pdf.ln()
示例#2
0
    def render(self, pdf: PDF, as_template=False):
        """
            render a question part onto the pdf.
            1. renders the question ctx, if any
            2. adds the gs grading anchor
            3. renders the given answer, if any
            4. pads until we've reached the max pages for the question.

            if as_template is true, will not render answer, and anchor will be empty.
        """
        start = pdf.page_no()
        render_part_header(
            pdf, f'Question {self.question_number}.{self.part}: {self.key}')
        pdf.set_font(get_cfg('font', 'body', 'font', default='arial'),
                     size=get_cfg('font', 'body', 'font', cast=int, default=10))
        # self.render_ctx(pdf)
        draw_line(pdf)
        render_gs_anchor(pdf, self.key, -1 if as_template else self.score)
        draw_line(pdf)
        self.render_expected(pdf)
        draw_line(pdf)
        self.render_ans(
            pdf) if not as_template else self.render_template_ans(pdf)
        pad_until(pdf, start + self.max_pages - 1,
                  f'padding for question {self.question_number}.{self.part}')
示例#3
0
    def render_file(self, pdf: PDF, filename, blank=False):
        '''
            Renders a file to a pdf. Does not start a fresh page.
            Pads out the pages with blank pages if the file does not exist
        '''
        path = self.files.get(filename, False)
        start = pdf.page_no()
        render_part_header(pdf, filename)

        if path:
            start = pdf.page_no()
            ext = os.path.splitext(path)[1][1:]
            font = get_cfg('font', 'code') if ext in get_cfg(
                'files', 'code') else get_cfg('font', 'body')
            pdf.set_font(font['font'], size=font['size'])
            if blank:
                pdf.cell(lineWidth, txt="This is a sample student answer.")
            elif ext in get_cfg('files', 'md'):
                pdf.write_html(to_latin1(markdown2.markdown_path(path)))
            elif ext in get_cfg('files', 'pics'):
                pdf.image(path, w=lineWidth)
            else:
                for line in open(path, 'r'):
                    pdf.multi_cell(lineWidth, lineHeight, txt=to_latin1(line))
        self.pad_from(pdf, start, filename)
示例#4
0
 def render_front_page(self, pdf: PDF, template=False):
     '''
         renders the title page for a student submission
     '''
     pdf.set_font(get_cfg('font', 'title', 'font', default="arial"),
                  size=get_cfg('font', 'title', 'size',
                               default=9, cast=int),
                  style="U")
     pdf.cell(0, 60, ln=1)
     id = self.uid if not template else " " * len(self.uid)
     name = self.name if not template else " " * len(self.name)
     pdf.cell(0, 20, f'Name: {name}', align='L')
     pdf.cell(0, 20, f'SID: {self.sid}', align='L')
     pdf.cell(0, 20, f'Email: {id}@berkeley.edu', align='L')
示例#5
0
def render_header(pdf: PDF, txt, header_cfg=get_cfg('font', 'header')):
    '''
        renders a question header with the given text.
    '''
    pdf.set_font(header_cfg['font'], size=header_cfg['size'])
    pdf.cell(lineWidth, txt=txt)
    draw_line(pdf, pdf.get_string_width(txt), header_cfg['line'])
示例#6
0
 def __init__(self, question_number: int, part: int, key, score: int = 0, weight: int = 1, ctx='', true_ans='', ans=''):
     super().__init__(question_number, part, key, score, weight)
     self.ans = str(ans)
     self.ctx = ctx
     self.true_ans = str(true_ans)
     self.max_pages = get_cfg('maxPages', 'string', cast=int, default=1)
     self.score = -1
示例#7
0
 def __init__(self, question_number: int, part: int, key, score: int = 0, weight: int = 1, files=[], file_bundle=None):
     super().__init__(question_number, part, key, score, weight)
     self.files = files
     self.file_bundle = file_bundle
     self.max_pages = get_cfg("maxPages", "file",
                              cast=int, default=1) * len(files)
     self.score = -1 # for manually graded coding questions
示例#8
0
def draw_line(pdf: PDF, width=lineWidth, color=get_cfg('font', 'header', 'line', default={"r": 0, "b": 0, "g": 0})):
    '''
        draws a line on the page.
        by default, the line is the page width.
    '''
    pdf.ln(6)
    pdf.set_line_width(0.5)
    pdf.set_draw_color(color['r'], color['b'], color['g'])
    pdf.line(10, pdf.get_y(), 12 + width, pdf.get_y())
    pdf.ln(6)
示例#9
0
def merge(qmap_json,
          gs_csv,
          instance=1,
          method=get_cfg('questions', 'mergeMethod', default='partial')):
    '''
        given a gradescope csv and a plgspl question map (per student),
        generates a "manual grading" csv for pl
    '''
    if method == "partial":
        merge_partials(qmap_json, gs_csv, instance)
    elif method == "total":
        merge_total(qmap_json, gs_csv, instance)
    else:
        print("Unsupported merge method.")
示例#10
0
def to_pdf(info_json, manual_csv, file_dir=None, roster=None):
    submissions = dict()
    config = qs.AssignmentConfig()
    if roster:
        with open(roster) as r:
            roster_json = json.load(r)
    else:
        roster_json = {}
    # load the raw assignment config file
    cfg = json.load(open(info_json))
    out_file = cfg.get("title", "assignment").replace(" ", "_")
    zones = cfg['zones']
    print(f'Parsing config for {out_file}...')
    for z in zones:
        for i, raw_q in enumerate(z['questions']):
            parts = raw_q['parts'] if 'parts' in raw_q else []
            files = set(raw_q['files']) if 'files' in raw_q else set()
            if 'id' not in raw_q:
                vs = list(map(lambda q: q['id'], raw_q['alternatives']))
                q = qs.QuestionInfo(vs[0],
                                    i + 1,
                                    variants=vs,
                                    number_choose=raw_q['numberChoose'],
                                    parts=parts,
                                    expected_files=files)
            else:
                q = qs.QuestionInfo(raw_q['id'],
                                    i + 1,
                                    parts=parts,
                                    expected_files=files,
                                    is_mc=bool(raw_q.get("is_mc", False)))
            config.add_question(q)
    print(
        f'Parsed config. Created {config.get_question_count()} questions and {config.get_variant_count()} variants.',
        end='\n\n')

    # iterate over the rows of the csv and parse the data
    print(
        f'Parsing submissions from {manual_csv} and provided file directory (if any)'
    )
    manual = pd.read_csv(manual_csv)
    for i, m in manual.iterrows():
        uid_full = m.get('uid', m.get('UID'))
        roster_map = roster_json.get(uid_full)
        if roster_map:
            name, student_id = roster_map
        else:
            name, student_id = "Unknown", "Unknown"
        uid = str(uid_full).split("@", 1)[0]

        qid = m['qid']
        sid = m['submission_id']
        submission = submissions.get(uid)
        if not submission:
            submission = qs.Submission(uid, name, student_id)
            submissions[uid] = submission
        q = config.get_question(qid)
        if not q:
            continue

        # look for any files related to this question submission
        fns = []
        if file_dir:
            for fn in os.listdir(file_dir):
                # if it has the student id, and the qid_sid pair, count it as acceptable
                if fn.find(uid_full) > -1 and fn.find(
                        f'{qs.escape_qid(qid)}_{sid}'
                ) > -1 and qs.parse_filename(fn, qid) in q.expected_files:
                    fns.append(os.path.join(file_dir, fn))
                    q.add_file(os.path.join(file_dir, fn))
        submission.add_student_question(
            qs.StudentQuestion(q, m['params'], m['true_answer'],
                               m['submitted_answer'], m['partial_scores'],
                               qs.StudentFileBundle(fns, qid), qid))
    print(f'Created {len(submissions)} submission(s)..')

    pdf = PDF()

    def pdf_output(pdf, name, uid=""):
        # try:
        pdf.output(os.path.join(os.getcwd(), f'{out_file}_{name}-fa20mt1.pdf'))

    # except:
    #     print("Couldn't make pdf for submission {} with uid {}".format(name, uid))

    prev = 1
    expected_pages = 0
    template_submission = None
    missing_questions = []
    for i, (_, v) in enumerate(submissions.items()):
        v: qs.Submission
        start_page = pdf.page_no()
        v.render_submission(pdf,
                            config,
                            template_submission=template_submission)
        if i == 0:
            sample_pdf = PDF()
            v.render_submission(sample_pdf, config, True)
            template_submission = v
            pdf_output(sample_pdf, "sample")
            expected_pages = sample_pdf.page_no()
            max_submissions = get_cfg('gs', 'pagesPerPDF') / expected_pages
            if max_submissions < 1:
                print(
                    'Cannot create submissions given the current max page constraint.'
                )
                print('Please adjust your defaults.')
                exit(1)
            max_submissions = int(max_submissions)
        diff = pdf.page_no() - start_page
        if diff < expected_pages:
            missing_questions.append(v.uid)
        elif diff > expected_pages:
            print(
                f'Submission {i}, {v.uid} exceeds the sample template. Please make sure that the first submission is complete'
            )
            exit(1)
        while pdf.page_no() - start_page < expected_pages:
            pdf.add_page()
            pdf.cell(0, 20, f'THIS IS A BLANK PAGE', ln=1, align='C')

        if i != 0 and i % max_submissions == 0:
            pdf_output(pdf, f'{i - max_submissions + 1}-{i + 1}')
            prev = i + 1
            pdf = PDF()

    if prev < len(submissions) or len(submissions) == 1:
        pdf_output(pdf, f'{prev}-{len(submissions)}')
    if len(missing_questions) > 0:
        print(
            f'{len(missing_questions)} submissions are missing question submissions. Please make sure to manually pair them in gradescope!',
            missing_questions,
            sep="\n")

    json.dump({k: v.list_questions(config)
               for k, v in submissions.items()},
              open(f'{out_file}_qmap.json', 'w'))
示例#11
0
def render_part_header(pdf: PDF, txt):
    '''
        renders a part header with the given text.
    '''
    render_header(pdf, txt, header_cfg=get_cfg('font', 'subheader'))
示例#12
0
 def __init__(self, question_number: int, part: int, key, score: int = 0, weight: int = 1):
     self.question_number = question_number
     self.part = part
     self.key = key
     self.score = score
     self.max_pages = get_cfg('maxPages', 'default', cast=int, default=1)
示例#13
0
 def pad_from(self, pdf, start, filename):
     pad_until(pdf, start + get_cfg('maxPages', 'file', cast=int, default=1) - 1,
               f'padding for file {filename}')
示例#14
0
from plgspl.types import PDF
from enum import Enum
from functools import reduce
from typing import List, Dict
import json
import os
import re
import collections
from plgspl.cfg import cfg, get_cfg
import markdown2
from unidecode import unidecode

lineWidth = get_cfg('page', 'lineWidth', default=180, cast=int)
lineHeight = get_cfg('page', 'lineHeight', default=0, cast=int)


def to_latin1(s: str) -> str:
    return unidecode(s)


def draw_line(pdf: PDF, width=lineWidth, color=get_cfg('font', 'header', 'line', default={"r": 0, "b": 0, "g": 0})):
    '''
        draws a line on the page.
        by default, the line is the page width.
    '''
    pdf.ln(6)
    pdf.set_line_width(0.5)
    pdf.set_draw_color(color['r'], color['b'], color['g'])
    pdf.line(10, pdf.get_y(), 12 + width, pdf.get_y())
    pdf.ln(6)