diff --git a/pyproject.toml b/pyproject.toml index 521624e..3084225 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,3 +15,6 @@ grdr = "auto_grader.cli:main" [tool.setuptools.packages.find] where = ["src"] + +[tool.setuptools.package-data] +"auto_grader" = ["data/*"] diff --git a/src/auto_grader/cli.py b/src/auto_grader/cli.py index 6abde3f..c4f4eb8 100644 --- a/src/auto_grader/cli.py +++ b/src/auto_grader/cli.py @@ -7,6 +7,7 @@ import subprocess import shlex import tomllib import re +import difflib from unidecode import unidecode SUCCESS_BOX = "[\033[0;92mSUCCESS\033[0m]" @@ -65,6 +66,29 @@ def _weaken_file(filename, functions): f.write(new_code) +def _add_static_attribute(match): + prefix = match.group(1) + func_name = match.group(2) + suffix = match.group(3) + if "static" in prefix: + return match.group(0) + return f"{prefix} static {func_name}{suffix}" + + +def _staticize_file(filename, functions): + pattern = re.compile( + r"^(\s*(?:(?:\w+[\s\*]+)+))" + r"(" + "|".join(functions) + r")" + r"(\s*\([^)]*\)\s*\{)", + re.MULTILINE + ) + with open(filename, "r") as f: + code = f.read() + new_code = pattern.sub(_add_static_attribute, code) + with open(filename, "w") as f: + f.write(new_code) + + def _remove_static_attribute(match): prefix = match.group(1) func_name = match.group(2) @@ -87,17 +111,43 @@ def _unstaticize_file(filename, functions): f.write(new_code) +def _extract_prototypes_to_header(filename, functions, header_filename): + func_pattern = re.compile( + r"^(\s*(?:(?:\w+[\s\*]+)+))" + r"(" + "|".join(functions) + r")" + r"(\s*\([^)]*\)\s*\{)", + re.MULTILINE + ) + with open(filename, "r") as f: + code = f.read() + + matches = func_pattern.findall(code) + if not matches: + print(f"No functions found in {filename}") + return + + prototypes = [] + for prefix, func_name, args in matches: + proto = f"{prefix.strip()} static {func_name}{args.strip()[:-1]};" + prototypes.append(proto) + prototypes = list(dict.fromkeys(prototypes)) # preserve order + header_exists = os.path.exists(header_filename) + with open(header_filename, "w" if header_exists else "w") as header: + header.write('#include "solver.h"'+"\n") + for proto in prototypes: + header.write(proto + "\n") + + include_line = f'#include "{os.path.basename(header_filename)}"\n' + if include_line not in code: + new_code = include_line + code + with open(filename, "w") as f: + f.write(new_code) + + def is_submission_dir(dir_path): return os.path.isdir(dir_path) and "_assignsubmission_file" in dir_path -# def _make_names(dir_path): -# student_names = [unidecode(name[:name.find('_')].lower().replace( -# ' ', '_')) for name in os.listdir( -# dir_path) if is_submission_dir(os.path.join(dir_path, name))] -# return student_names - - def _make_names_pairs(dir_path): student_pairs = [(unidecode(name[:name.find('_')].lower().replace( ' ', '_')), os.path.join(dir_path, name)) for name in os.listdir( @@ -356,8 +406,6 @@ def compile(number, path, command, verbose): @click.argument('number', required=True, type=click.INT) @click.option('-s', '--solution-path', default='./solutions', type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True)) @click.option('-p', '--path', default='./roots', type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True)) -# @click.option('-c', '--command', default='make', type=click.STRING) -# @click.option('-v', '--verbose', is_flag=True, default=False) def dissect(number, solution_path, path): sol_as_path = os.path.join(solution_path, f"assignment_{number}") config_path = os.path.join(sol_as_path, "test_config.toml") @@ -384,6 +432,11 @@ def dissect(number, solution_path, path): f"{ERROR_BOX}: No function dissect config found for assignment_{number}.", err=True) return all_functions = config['dissect']['all_functions'] + if not 'build_cmd' in config["dissect"]: + click.echo( + f"{ERROR_BOX}: No build config found for assignment_{number}.", err=True) + return + static_functions = config['dissect']['static_functions'] if not 'build_cmd' in config["dissect"]: click.echo( f"{ERROR_BOX}: No build config found for assignment_{number}.", err=True) @@ -428,11 +481,17 @@ def dissect(number, solution_path, path): click.echo( f"\t[{os.path.basename(root)}]:{ERROR_BOX}: Missing source {os.path.basename(src)}") shutil.copyfile(src, dest) + + _extract_prototypes_to_header( + dest, static_functions, os.path.join(sol_as_path, 'src', 'aid.h')) + _unstaticize_file(dest, all_functions) _weaken_file(dest, all_functions) - # BUILD TEST + _staticize_file(dest, static_functions) + result = 0 os.chdir(sol_as_path) + for function, flag in zip(test_functions, flags): command = build_cmd + f" CFLAGS+={flag}" result = subprocess.run( @@ -446,13 +505,9 @@ def dissect(number, solution_path, path): else: click.echo( f"\t[{os.path.basename(root)}]:[BUILD]:[{function}]:{ERROR_BOX}") - click.echo(f"stderr: {result.stderr[:69]:69s}...") + click.echo(f"\t\tstderr: {result.stderr[:61]:61s}...") os.chdir(current_wd) - # for file in solution_files: - # _unstaticize_file(file, test_functions) - # _weaken_file(file, all_functions) - cli.add_command(init) cli.add_command(archives)