diff --git a/setup.py b/setup.py index 7a0d08d83ee37bc234d1367a23cdb50f86d8c760..98e942edd0c359b6c8629efbecf1625d7b1ee4e0 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.2", + version="0.1.18.3", 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 6c31fbb9b73e78b53fe8367d14d6c9e5d6a9aec1..6c13ff80a7ee50f0c91fc71828c7d39338428983 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.2 +Version: 0.1.18.3 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.egg-info/SOURCES.txt b/src/coursebox.egg-info/SOURCES.txt index 4941d7b6511f261f67a98c8796a31787b27886c4..840504141bfb28bd30dd0fbeeece732d9a012e32 100644 --- a/src/coursebox.egg-info/SOURCES.txt +++ b/src/coursebox.egg-info/SOURCES.txt @@ -22,5 +22,7 @@ src/coursebox/material/__init__.py src/coursebox/material/homepage_lectures_exercises.py src/coursebox/material/lecture_questions.py src/coursebox/material/snipper.py +src/coursebox/student_files/__init__.py +src/coursebox/student_files/student_files.py src/coursebox/testing/__init__.py src/coursebox/testing/testing.py \ No newline at end of file diff --git a/src/coursebox/core/info_paths.py b/src/coursebox/core/info_paths.py index dec085b143bec235e4baf439f7353eaf0497ada1..2bd47359137dcc3925a9815c1b02a7387e1e1ea9 100644 --- a/src/coursebox/core/info_paths.py +++ b/src/coursebox/core/info_paths.py @@ -21,15 +21,6 @@ def get_paths(): root_02450public = root_02450public.replace("\\", "/") root_02450private = root_02450private.replace("\\", "/") - - # if not os.path.isdir(root_02450private): - # root_02450private = f'{root_02450public}/{num}private' - # warn('Private repository not found at the expected location.') - # warn('Using mock info from resources folder at:') - # warn(root_02450private) - # Tue: always overwrite semester path. - # semester_path = root_02450private +"/resources/mock_semesters/" + semester_id() - # else: semester_path = root_02450private + "/semesters/" + semester_id() if not os.path.isdir(semester_path): diff --git a/src/coursebox/student_files/__init__.py b/src/coursebox/student_files/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/coursebox/student_files/student_files.py b/src/coursebox/student_files/student_files.py new file mode 100644 index 0000000000000000000000000000000000000000..2a248f53914fa43225aa2959098150eab965094f --- /dev/null +++ b/src/coursebox/student_files/student_files.py @@ -0,0 +1,220 @@ +import os, shutil +from coursebox.core.info_paths import get_paths +from coursebox.core.info import class_information +from snipper.snip_dir import snip_dir +from coursebox.material.homepage_lectures_exercises import fix_shared +from snipper.snipper_main import censor_file +from pathlib import Path +import fnmatch + +def setup_student_files(run_files=True, + cut_files=True, + censor_files=True, + week=None, extra_dirs=None, + include_solutions=None, + projects=None, + setup_lectures=False, strict=True, + fix_shared_files=True, + include_exam_examples=False, # Include the directory with exam-related examples. + verbose=True, + students_irlc_tools=None, # Destination dir for the irlc folder (the one we really want to deploy). + midterms=None, # Can be a list: [0, 1] + include_docs=False, # Include the documentation directory, i.e. 02465public/pythontools/docs. + convert_svg=True, # Whether to convert SVG file when deploying. Leave to true, unless we are doing CI/CD in which case inkscape may not be installed. + package="cp", + fix_shared_files_func=None, + ): + + if midterms is None: + midterms = [] + from coursebox.core import info_paths + + week = [week] if isinstance(week, int) else week + if verbose: + print("toolbox running. Fixing student files by copying to student dir") + paths = get_paths() + if include_solutions is None: + include_solutions = [] + + if projects is None: + projects = [] + + public_irlc_tools = paths['02450public'] + "/src/cp" + + if students_irlc_tools is None: + if censor_files: + students_irlc_tools = paths['02450students'] + "/cp" + else: + students_irlc_tools = paths['02450students'] + "_complete/cp" + + if not os.path.isdir(students_irlc_tools): + os.makedirs(students_irlc_tools) + + init_dest = students_irlc_tools+"/__init__.py" + shutil.copy(public_irlc_tools + "/__init__.py", init_dest) + censor_file(init_dest) + + # Check for exclusion mask. + exclude = info_paths.core_conf.get('student_files', {}).get('exclude', []) + + exclude += [f'tests_week{w if w >= 10 else f"0{w}"}.py' for w in range(0,14) if w not in week] + exclude = exclude + ["**/" + ex for ex in exclude] + if not include_exam_examples: + exclude += ['exam_tabular_examples'] + inclusion = [] # 'ex09old/cache'] + hws = [] + for m in midterms: + # Copy the midterm from the exam directory into the public repository. This will ensure the midterm is later propagated to the student directory. + # The midterm should always be distributed as a .zip file: This is the only legal distribution mechanism. + # The midterm in the student directory does not need to be super-duper up to date. This is not the file which is distributed 'officially'. + if verbose: + print(m) + + if extra_dirs is None: + extra_dirs = ['utils', 'tests', 'exam/exam2023spring'] # 'assignments', + for m in midterms: + if m == 0: + extra_dirs += ['exam/midterm2023a'] + if m == 1: + extra_dirs += ['exam/midterm2023b'] + + # if include_exam_examples: + # extra_dirs += ['exam_tabular_examples'] + if setup_lectures: + extra_dirs += [ + # 'lectures/chapter0pythonC', + # 'lectures/chapter0pythonA', + # 'lectures/chapter0pythonB', + # 'lectures/chapter1', + 'lectures', + 'workshop' + ] + + import coursebox.core.info + + for id in projects: + # if setup_projects: + if 'projects_all' in info_paths.core_conf: + p = info_paths.core_conf['projects_all'][id]['class'] + extra_dirs += [os.path.basename(os.path.dirname(p.mfile()))] + else: + + if id <= 3: + extra_dirs += [f'project{id}'] # , 'project1', 'project2', 'project3'] + else: + extra_dirs.append(f'project3i') + + if week is not None: + for w in week: + ex = str(w) + ex = "ex" + ("0" + ex if len(ex) < 2 else ex) + base = public_irlc_tools +"/"+ex + if not os.path.isdir(base): + continue + d = {'base': base, + 'out': students_irlc_tools +"/"+ex, + 'exclusion': exclude, + 'inclusion': inclusion, + 'include_solutions': w in include_solutions, + 'week': w} + hws.append(d) + + weekn = 101 + for weekn, d in enumerate(extra_dirs): + dutil = {'base': public_irlc_tools + "/" + d, + 'out': students_irlc_tools + "/" + d, + 'exclusion': exclude, + 'inclusion': inclusion, + 'include_solutions': False, + 'week': f'{weekn+100}'} + hws.append(dutil) + weekn += 1 + + else: + raise Exception("Specify a week") + + if len(hws) > 0: + info = class_information(verbose=verbose) + # print("references") # Bad idea because of the very long output. + # print(info['references']['commands']) + else: + info = None + for hw in hws: + n = fix_hw(paths=paths, info=info, hw=hw, out= hw['out'], output_dir=paths['shared'] +"/output", run_files=run_files, cut_files=cut_files, + package_base_dir=os.path.dirname(students_irlc_tools), censor_files=censor_files, strict=strict, + include_solutions=hw.get('include_solutions', False), + verbose=verbose) + + with open(paths['shared'] + f"/output/lines_{hw['week']}.txt", 'w') as f: + f.write(str(n)) + + + if fix_shared_files: + if verbose: + print("> Homework fixed, copying shared files...") + fix_shared_files_func(paths=paths, compile_templates=False, verbose=verbose, dosvg=convert_svg) + + if verbose: + print("> Removing excluded files from students gitlab directory...") + # for f in exclude + ["lectures"]: + for l in list(Path(students_irlc_tools).glob('**/*')): + if not os.path.exists(l): # May have been removed as part of a subtree + continue + m = [ ex for ex in exclude if fnmatch.fnmatch(l, ex)] + if len(m) > 0: + if os.path.isdir(l): + shutil.rmtree(l) + else: + os.remove(l) + + shutil.copy(public_irlc_tools +"/../requirements.txt", students_irlc_tools +"/../requirements.txt") + # shutil.copy(public_irlc_tools +"/../requirements_conda.txt", students_irlc_tools +"/../requirements_conda.txt") + # Don't think we need this anymore. Why copy docs if they cannot be compiled anyway? Perhaps for the complete docs? + if include_docs: + if os.path.isdir(students_irlc_tools + "/../docs"): + shutil.rmtree(students_irlc_tools + "/../docs") + # if not censor_files: + shutil.copytree(public_irlc_tools + "/../docs", students_irlc_tools + "/../docs") + # Fix reference to output. + snip_dir(public_irlc_tools + "/../docs/source", students_irlc_tools + "/../docs/source", run_files=False, cut_files=False, censor_files=False, references=info['references'], verbose=verbose) + if verbose: + print("> All student file stuff completed.") + + + +def fix_hw(paths, info, hw, out, output_dir, run_files=False, cut_files=False, censor_files=True, + include_solutions=False, + package_base_dir=None, # When runing files using #!o, this specify the base directory of the package the files resides in. Can be None. + verbose=True, + **kwargs): + + n, cutouts = snip_dir(source_dir=hw['base'], dest_dir=out, output_dir=output_dir, exclude=hw['exclusion'], references=info['references'], + run_files=run_files, cut_files=cut_files, license_head=info.get('code_copyright', None), + censor_files=censor_files,verbose=verbose,package_base_dir=package_base_dir) + + if include_solutions: + wk = hw['base'].split("/")[-1] + sp = paths['02450students'] + "/solutions/" + if not os.path.exists(sp): + os.mkdir(sp) + sp = sp + wk + if os.path.isdir(sp): + shutil.rmtree(sp) + # if not os.path.exists(sp): + os.mkdir(sp) + + for f, cut in cutouts.items(): + if len(cut) > 0: + fname = os.path.basename(f) + sols = [] + stext = ["\n".join(lines) for lines in cut] + for i, sol in enumerate(stext): + sols.append((sol,)) + sout = sp + f"/{os.path.basename(fname)[:-3]}_TODO_{i + 1}.py" + wsol = True + print(sout, "(published)" if wsol else "") + if wsol: + with open(sout, "w") as f: + f.write(sol) + + return n