diff --git a/setup.py b/setup.py index adea4d67c810b266cac64abb21dfd1de5cda0ae6..87aa6d9f317426d601b42ba3cd1a5321871aef96 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ with open("README.md", "r", encoding="utf-8") as fh: # beamer-slider setuptools.setup( name="coursebox", - version="0.1.18.6", + version="0.1.18.8", author="Tue Herlau", author_email="tuhe@dtu.dk", description="A course management system currently used at DTU", diff --git a/src/coursebox.egg-info/PKG-INFO b/src/coursebox.egg-info/PKG-INFO index b2d8dc646134f57d3b31c90b134d812c654a2625..ded783a19bbff26efc1a0b60568236a727a6d0ce 100644 --- a/src/coursebox.egg-info/PKG-INFO +++ b/src/coursebox.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: coursebox -Version: 0.1.18.6 +Version: 0.1.18.8 Summary: A course management system currently used at DTU Home-page: https://lab.compute.dtu.dk/tuhe/coursebox Author: Tue Herlau diff --git a/src/coursebox/core/info.py b/src/coursebox/core/info.py index c3d81c1565a060c1a9315e76ba366bf0829fffff..efe3a417bbf1ba09cd85da12b7db8dd7a78f16a9 100644 --- a/src/coursebox/core/info.py +++ b/src/coursebox/core/info.py @@ -286,6 +286,8 @@ def class_information(verbose=False, info = pickle.load(f) info = _update_with_core_conf(info) return info + elif verbose: + print("Coursebox> Loaded configurations from file", paths['information.xlsx']) course_number = core_conf['course_number'] piazza = 'https://piazza.com/dtu.dk/%s%s/%s' % (semester().lower(), year(), course_number) @@ -294,6 +296,10 @@ def class_information(verbose=False, continuing_education_mode = core_conf['continuing_education_mode'] faq = xlsx_to_dicts(paths['information.xlsx'], sheet='faq') + # sections = xlsx_to_dicts(paths['information.xlsx'], sheet='teachers') + if (sections := xlsx_to_dicts(paths['information.xlsx'], sheet='sections')) is not None: + sections = {v['id']: v for v in sections} + d = {'year': year(), 'piazza': piazza, # deprecated. 'course_number': course_number, @@ -436,10 +442,21 @@ def class_information(verbose=False, dd = timedelta(days=l.get('show_solutions_after', 1)) d['release_rules'][str(n)] = dict(start=date+dd, end=date+timedelta(days=2000)) + # Update with section information. + if sections is not None: + for l in d['lectures']: + # print(l['number']) + + l['date_sections'] = {s['id'] : {'date': l['date'] + timedelta(days=int(s['lecture_date_delta']) ) } for s in sections.values() } + l['date_sections'] = {k: {**v, **date2format(v['date']) } for k, v in l['date_sections'].items() } + l['teacher_initials_sections'] = {k: v.strip() for k, v in zip( list( sections.keys() ), l['teacher_initials'].split("/") ) } + if update_with_core_conf: d = _update_with_core_conf(d) + d['sections'] = sections + return d def _update_with_core_conf(d): diff --git a/src/coursebox/material/homepage_lectures_exercises.py b/src/coursebox/material/homepage_lectures_exercises.py index c6e122de68e90e3f79aa0565f2f33c5fa6364782..d844b814e279529b0feecaf8a8c220498367839a 100644 --- a/src/coursebox/material/homepage_lectures_exercises.py +++ b/src/coursebox/material/homepage_lectures_exercises.py @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- import shutil, os, glob from datetime import datetime, timedelta import calendar import pickle import time -# from line_profiler_pycharm import profile from coursebox.thtools_base import partition_list - import slider from jinjafy import jinjafy_comment from jinjafy import jinjafy_template @@ -19,9 +16,12 @@ from coursebox.core.info import class_information from coursebox.material.lecture_questions import lecture_question_compiler from slider import latexmk import coursebox -# from line_profiler_pycharm import profile +import asyncio +import time +from jinjafy.cache.simplecache import hash_file_ +import tempfile -def get_feedback_groups(): +def get_feedback_groups(): # This is really getting deprecated... paths = get_paths() feedback_file = paths['semester'] +"/feedback_groups.pkl" if os.path.exists(feedback_file): @@ -66,16 +66,13 @@ def get_feedback_groups(): sum( [len(v) for k,v in fbg.items() ]) - sum( [len( set(v)) for k,v in fbg.items() ]) with open(feedback_file, 'wb') as f: - pickle.dump(fbg,f) - + pickle.dump(fbg, f) for k in fbg: g = fbg[k] g2 = [] for s in g: - if s in info['students']: dl = info['students'][s]['firstname'] + " " + info['students'][s]['lastname'] - # dl = [ss['firstname']+" "+ss['lastname'] for ss in if ss['id'] == s] if not dl: print("EMPTY LIST when making feedback groups. Probably an error in project correction sheets.") continue @@ -86,7 +83,7 @@ def get_feedback_groups(): PRESENTATION = 0 NOTES = 1 HANDOUT = 2 -def make_lectures(week=None, mode=0, gather_pdf_out=True, gather_sixup=True, make_quizzes=True, dosvg=False, Linux=False): +def make_lectures(week=None, mode=0, gather_pdf_out=True, gather_sixup=True, make_quizzes=True, dosvg=False, async_pool=-1): """ Mode determines what is compiled into the pdfs. It can be: @@ -94,6 +91,8 @@ def make_lectures(week=None, mode=0, gather_pdf_out=True, gather_sixup=True, mak mode = NOTES = 1: Version containing notes (used for self-study) mode = HANDOUT = 2: version handed out to students. """ + assert mode in [PRESENTATION, NOTES, HANDOUT] + paths = get_paths() if os.path.exists(paths['book']): book_frontpage_png = paths['shared']+"/figures/book.png" #paths['lectures']+"/static/book.png" @@ -104,81 +103,175 @@ def make_lectures(week=None, mode=0, gather_pdf_out=True, gather_sixup=True, mak # course_number = info['course_number'] if isinstance(week, int): week = [week] - all_pdfs = [] - for lecture in info['lectures']: - w = lecture['number'] - if week is not None and w not in week: - continue - ag = get_feedback_groups() - - lecture['feedback_groups'] = ag.get(w, []) - info.update({'week': w}) - info['lecture'] = lecture - info['lecture']['teacher'] = [t for t in info['teachers'] if t['initials'] == lecture['teacher_initials']].pop() - - lecture_texdir = paths['lectures'] + '/Lecture_%s/Latex' % w - lecture_texfile =lecture_texdir + "/Lecture_%i.tex" % w - - fix_shared(paths, output_dir=lecture_texdir, dosvg=dosvg) - - if os.path.exists(lecture_texdir): - print("Latex directory found for lecture %i: %s"%(w,lecture_texdir)) - lecture_texdir_generated = lecture_texdir +"/templates" - if not os.path.exists(lecture_texdir_generated): - # shutil.rmtree(lecture_texdir_generated) - # time.sleep(0.2) - os.mkdir(lecture_texdir_generated) - - if mode == PRESENTATION: - info['slides_shownotes'] = False - info['slides_handout'] = False - odex = "/presentation" - elif mode == HANDOUT: - info['slides_shownotes'] = False - info['slides_handout'] = True - info['slides_showsolutions'] = False - odex = "/handout" - elif mode == NOTES: - info['slides_shownotes'] = True - info['slides_handout'] = True - odex = "/notes" + lectures_to_compile = [lecture for lecture in info['lectures'] if week is None or lecture['number'] in week] + t = time.time() + + if async_pool <= 1: + all_pdfs = [] + for lecture in lectures_to_compile: #info['lectures']: + pdf_out = _compile_single_lecture(dosvg=dosvg, lecture=lecture, make_quizzes=make_quizzes, mode=mode) + all_pdfs.append( (lecture['number'], pdf_out)) + else: + # Do the async here. + cp_tasks = [] + for lecture in lectures_to_compile: #info['lectures']: + tsk = _compile_single_lecture_async(dosvg=dosvg, lecture=lecture, make_quizzes=make_quizzes, mode=mode) + cp_tasks.append(tsk) + loop = asyncio.get_event_loop() + cm = asyncio.gather(*cp_tasks) + results = loop.run_until_complete(cm) + all_pdfs = [ (lecture['number'], pdf) for (lecture, pdf) in zip( lectures_to_compile, results ) ] + # raise Exception("no async here") + # pass + t2 = time.time() - t + print("main compile, t1, t2", t2) + + if mode == PRESENTATION: + odex = "/presentation" + elif mode == HANDOUT: + odex = "/handout" + elif mode == NOTES: + odex = "/notes" + else: + odex = None + + t = time.time() + if len(all_pdfs) and gather_pdf_out > 0: + if async_pool >= 2: + # if len(all_pdfs) > 0 and gather_pdf_out: + async_handle_pdf_collection(paths, all_pdfs, gather_sixup=gather_sixup, odir=odex) else: - raise Exception("Mode not recognized") - - for f in glob.glob(paths['lectures'] + "/templates/*.tex"): - ex = "_partial.tex" - if f.endswith(ex): - # print(info) - print("Building file", f) - jinjafy_template(info, file_in=f, file_out=lecture_texdir + "/templates/"+os.path.basename(f)[:-len(ex)] + ".tex") - - # Fix questions. - qtarg = lecture_texdir + "/questions" - if not os.path.exists(qtarg): - os.mkdir(qtarg) - for f in glob.glob(paths['lectures'] + "/static/questions/*"): - shutil.copy(f, qtarg) - - # Fix questions for this lecture - if make_quizzes: - lecture_question_compiler(paths, info, lecture_texfile) - - print("Making file", lecture_texfile, Linux) - # pdf_out = slider.latexmk(lecture_texfile, Linux=Linux) - try: - pdf_out = slider.latexmk(lecture_texfile, Linux=Linux) - except Exception as e: - log = lecture_texfile[:-4] + ".log" - print("loading log", log) - with open(log, 'r') as f: - print(f.read()) - raise e - all_pdfs.append( (w,pdf_out)) - - if len(all_pdfs) > 0: - handle_pdf_collection(paths, all_pdfs, gather_pdf_out=gather_pdf_out, gather_sixup=gather_sixup, odir=odex) + # if len(all_pdfs) > 0: + handle_pdf_collection(paths, all_pdfs, gather_pdf_out=gather_pdf_out, gather_sixup=gather_sixup, odir=odex) + t2 = time.time() - t + print("t1, t2", t2) + a = 234 + +def _setup_lecture_info(lecture, mode, dosvg, make_quizzes): + w = lecture['number'] + info = class_information() + paths = get_paths() + # if week is not None and w not in week: + # continue + ag = get_feedback_groups() + lecture['feedback_groups'] = ag.get(w, []) + info.update({'week': w}) + info['lecture'] = lecture + info['lecture']['teacher'] = [t for t in info['teachers'] if t['initials'] == lecture['teacher_initials']].pop() + lecture_texdir = paths['lectures'] + '/Lecture_%s/Latex' % w + lecture_texfile = lecture_texdir + "/Lecture_%i.tex" % w + fix_shared(paths, output_dir=lecture_texdir, dosvg=dosvg) + # if os.path.exists(lecture_texdir): + # print("Latex directory found for lecture %i: %s" % (w, lecture_texdir)) + # # lecture_texdir_generated = lecture_texdir + "/templates" + # # if not os.path.exists(lecture_texdir_generated): + # # os.mkdir(lecture_texdir_generated) + if mode == PRESENTATION: + info['slides_shownotes'] = False + info['slides_handout'] = False + # odex = "/presentation" + elif mode == HANDOUT: + info['slides_shownotes'] = False + info['slides_handout'] = True + info['slides_showsolutions'] = False + # odex = "/handout" + elif mode == NOTES: + info['slides_shownotes'] = True + info['slides_handout'] = True + # odex = "/notes" + for f in glob.glob(paths['lectures'] + "/templates/*.tex"): + ex = "_partial.tex" + if f.endswith(ex): + jinjafy_template(info, file_in=f, + file_out=lecture_texdir + "/templates/" + os.path.basename(f)[:-len(ex)] + ".tex") + + # Fix questions. + qtarg = lecture_texdir + "/questions" + if not os.path.exists(qtarg): + os.mkdir(qtarg) + for f in glob.glob(paths['lectures'] + "/static/questions/*"): + shutil.copy(f, qtarg) + if make_quizzes: + lecture_question_compiler(paths, info, lecture_texfile) + + return lecture_texfile + pass + +def _compile_single_lecture(dosvg, lecture, make_quizzes, mode): + lecture_texfile = _setup_lecture_info(lecture, mode, dosvg, make_quizzes) + # Fix questions for this lecture + try: + pdf_out = slider.latexmk(lecture_texfile) + except Exception as e: + log = lecture_texfile[:-4] + ".log" + print("loading log", log) + with open(log, 'r') as f: + print(f.read()) + raise e + return pdf_out + +async def _compile_single_lecture_async(dosvg, lecture, make_quizzes, mode): + lecture_texfile = _setup_lecture_info(lecture, mode, dosvg, make_quizzes) + # Fix questions for this lecture + try: + pdf_out = slider.latexmk_async(lecture_texfile) + except Exception as e: + log = lecture_texfile[:-4] + ".log" + print("loading log", log) + with open(log, 'r') as f: + print(f.read()) + raise e + return pdf_out + # http://piazza.com/dtu.dk/spring2023/02465/home +def async_handle_pdf_collection(paths, all_pdfs, gather_sixup, odir): + # tmp_dir = paths['lectures'] + '/Collected/tmp' + # if not os.path.isdir(tmp_dir): + # os.mkdir(tmp_dir) + import tempfile + tasks = [] + for sixup in [False, True]: + if sixup and not gather_sixup: continue + for (week, _) in all_pdfs: + tasks.append(_compile_single(paths, sixup, week)) + + loop = asyncio.get_event_loop() + cm = asyncio.gather(*tasks) + pdf_compiled_all_6up = loop.run_until_complete(cm) + for f in pdf_compiled_all_6up: + assert os.path.isfile(f) + + for dpdf in pdf_compiled_all_6up: + output_dir = paths['pdf_out'] + odir + if not os.path.exists(output_dir): + os.makedirs(output_dir) + shutil.copy(dpdf, output_dir + "/" + os.path.basename(dpdf)) + + # for f in glob.glob(tmp_dir + "/*"): + # os.remove(f) + + +async def _compile_single(paths, sixup, week): + # tmp_dir = tempfile.gettempdir() + tmp_dir = tempfile.mkdtemp() + if True: + # with tempfile.TemporaryDirectory() as tmp_dir: + # if not os.path.isdir(tmp_dir): + # os.mkdir(tmp_dir) + collect_template = paths['lectures'] + "/Collected/lecture_collector_partial.tex" + sixup_str = "-6up" if sixup else "" + tv = {'week': week, + 'pdffiles': [paths['lectures'] + '/Lecture_%s/Latex/Lecture_%s.pdf' % (week, week)]} + if sixup: + tv['sixup'] = sixup + tex_out_sixup = tmp_dir + "/Lecture_%i%s.tex" % (week, sixup_str) + jinjafy_comment(data=tv, file_in=collect_template, file_out=tex_out_sixup, jinja_tag=None) + dpdf = await slider.latexmk_async(tex_out_sixup, cleanup=True) + else: + dpdf = tv['pdffiles'][0] + return dpdf + def handle_pdf_collection(paths, all_pdfs, gather_pdf_out, gather_sixup, odir): tmp_dir = paths['lectures'] + '/Collected/tmp' @@ -229,15 +322,13 @@ def compile_simple_files(paths, info, template_file_list, verbose=False): latexmk(tex_out, pdf_out= paths['pdf_out'] + "/" + os.path.basename(tex_out)[:-4]+".pdf") # rec_fix_shared(shared_base=paths['shared'], output_dir=output_dir) -import time + # import dirsync # dirsync.sync(paths['shared'], output_dir, 'diff') - - # Do smarter fixin' from pathlib import Path -from jinjafy.cache.simplecache import hash_file_ + # @profile def get_hash_from_base(base): @@ -514,7 +605,7 @@ def slide_converter(week=None, verbose=True, clean_temporary_files=False, copy_t pdf_out = texdir +"/Lecture_%i.pdf"%n shutil.copyfile(pdf_in, pdf_out) - print("operating...") + print("Importing slides OSVGS slides since they were deleted...") lecture_tex_out = li_import(pdf_out, output_dir=texdir) print("Wrote new main file: " + lecture_tex_out) else: @@ -528,5 +619,4 @@ def slide_converter(week=None, verbose=True, clean_temporary_files=False, copy_t clean_temporary_files=clean_temporary_files, copy_template_resource_files=copy_template_resource_files, fix_broken_osvg_files=fix_broken_osvg_files, **kwargs) - print("Slides converted!")