def compile_all(exam, subtitle, out, do_twice, email, exam_type, semester, deadline): """ Compile individualized PDFs for the specified exam. Exam must have been deployed first. """ if not out: out = "out/latex/" + exam pathlib.Path(out).mkdir(parents=True, exist_ok=True) exam_data = get_exam(exam=exam) password = exam_data.pop("secret")[:-1] print(password) exam_str = json.dumps(exam_data) roster = get_roster(exam=exam) if email: roster = [line_info for line_info in roster if line_info[0] == email] if len(roster) == 0: if deadline: roster = [(email, deadline)] else: raise ValueError("Email does not exist in the roster!") for email, deadline in roster: if not deadline: continue exam_data = json.loads(exam_str) scramble(email, exam_data) deadline_utc = datetime.utcfromtimestamp(int(deadline)) deadline_pst = pytz.utc.localize(deadline_utc).astimezone( pytz.timezone("America/Los_Angeles")) deadline_string = deadline_pst.strftime("%I:%M%p") with render_latex( exam_data, { "emailaddress": sanitize_email(email), "deadline": deadline_string, "coursecode": prettify(exam.split("-")[0]), "description": subtitle, "examtype": exam_type, "semester": semester, }, do_twice=do_twice, ) as pdf: pdf = Pdf.open(BytesIO(pdf)) pdf.save( os.path.join( out, "exam_" + email.replace("@", "_").replace(".", "_") + ".pdf"), encryption=Encryption(owner=password, user=password), ) pdf.close()
def compile(exam, json, md, seed, json_out, out): """ Compile one PDF or JSON (from Markdown), unencrypted. The exam may be deployed or local (in Markdown or JSON). If a seed is specified, it will scramble the exam. """ if not out: out = "" pathlib.Path(out).mkdir(parents=True, exist_ok=True) if json: print("Loading exam...") exam_data = load(json) elif md: print("Compiling exam...") exam_text_data = md.read() exam_data = convert(exam_text_data) else: print("Fetching exam...") exam_data = get_exam(exam=exam) if seed: print("Scrambling exam...") exam_data = scramble( seed, exam_data, ) if json_out: print("Dumping json...") dump(exam_data, json_out) return print("Rendering exam...") with render_latex(exam_data, { "coursecode": prettify(exam.split("-")[0]), "description": "Sample Exam." }) as pdf: pdf = Pdf.open(BytesIO(pdf)) pdf.save(os.path.join(out, exam + ".pdf")) pdf.close()
def send(exam, target, email, subject, filename): """ Email an encrypted PDF to all students taking an exam. Specify `email` to email only a particular student. """ if not target: target = "out/latex/" + exam course = prettify(exam.split("-")[0]) filename = filename.format(course=course) subject = subject.format(course=course) body = ( "Hello!\n\n" "You have an upcoming exam taking place on exam.cs61a.org. " "You should complete your exam on that website.\n\n" "Course: {course}\n" "Exam: {exam}\n\n" "However, if you encounter technical difficulties and are unable to do so, " "we have attached an encrypted PDF containing the same exam. " "You can then email your exam solutions to course staff before the deadline " "rather than submitting using exam.cs61a.org. " "To unlock the PDF, its password will be revealed on Piazza when the exam starts.\n\n" "Good luck, and remember to have fun!").format(course=course, exam=exam) roster = [] if email: roster = [email] else: for email, deadline in get_roster(exam=exam): if deadline: roster.append(email) key = get_api_key(exam=exam) print(("Subject: {subject}\n" "PDF filename: {filename}\n" "Body: {body}\n\n").format(body=body, filename=filename, subject=subject)) if (input("Sending email to {} people - confirm? (y/N) ".format( len(roster))).lower() != "y"): exit(1) for email in roster: with open( os.path.join( target, "exam_" + email.replace("@", "_").replace(".", "_") + ".pdf"), "rb", ) as f: pdf = base64.b64encode(f.read()).decode("ascii") data = { "from": { "email": "*****@*****.**", "name": "CS 61A Exam Platform" }, "personalizations": [{ "to": [{ "email": email }], "substitutions": {} }], "subject": subject, "content": [{ "type": "text/plain", "value": body }], "attachments": [{ "content": pdf, "type": "application/pdf", "filename": filename, "disposition": "attachment", }], } send_email_local(key, data)
def compile( exam, json, md, seed, subtitle, with_solutions, exam_type, semester, json_out, merged_md, draft, out, ): """ Compile one PDF or JSON (from Markdown), unencrypted. The exam may be deployed or local (in Markdown or JSON). If a seed is specified, it will scramble the exam. """ if not out: out = "" pathlib.Path(out).mkdir(parents=True, exist_ok=True) if json: print("Loading exam...") exam_data = load(json) elif md: exam_text_data = md.read() if merged_md: buff = LineBuffer(exam_text_data) handle_imports(buff, path=os.path.dirname(md.name)) merged_md.write("\n".join(buff.lines)) return print("Compiling exam...") exam_data = convert(exam_text_data, path=os.path.dirname(md.name), draft=draft) else: print("Fetching exam...") exam_data = get_exam(exam=exam) if seed: print("Scrambling exam...") exam_data = scramble(seed, exam_data, keep_data=with_solutions) def remove_solutions_from_groups(groups): for group in groups: # if isinstance(group, dict): group.pop("solution", None) if group.get("type") == "group": remove_solutions_from_groups(group.get("elements", [])) if not seed and not with_solutions: print("Removing solutions...") groups = exam_data.get("groups", []) remove_solutions_from_groups(groups) if json_out: print("Dumping json...") dump(exam_data, json_out, indent=4, sort_keys=True) return print("Rendering exam...") settings = { "coursecode": prettify(exam.split("-")[0]), "description": subtitle, "examtype": exam_type, "semester": semester, } if seed: settings["emailaddress"] = sanitize_email(seed) with render_latex(exam_data, settings) as pdf: pdf = Pdf.open(BytesIO(pdf)) pdf.save(os.path.join(out, exam + ".pdf")) pdf.close()