diff options
author | jwansek <eddie.atten.ea29@gmail.com> | 2021-11-15 19:46:31 +0000 |
---|---|---|
committer | jwansek <eddie.atten.ea29@gmail.com> | 2021-11-15 19:46:31 +0000 |
commit | d262c125550dfb952abeb1c953731f470c52decd (patch) | |
tree | 9b46fae13dbf589f8a7165ccb133794c97d049cf | |
parent | 4a0b15e2aba519d184f36f330a7b4af05bc2fd7d (diff) | |
download | Smarker-d262c125550dfb952abeb1c953731f470c52decd.tar.gz Smarker-d262c125550dfb952abeb1c953731f470c52decd.zip |
made a start on analysis
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | ExampleAssessments/CMP-4009B.yml | 6 | ||||
-rw-r--r-- | ExampleAssessments/smol.yml | 21 | ||||
-rw-r--r-- | mark.py | 46 | ||||
-rw-r--r-- | reflect.py | 60 | ||||
-rw-r--r-- | reportWriter.py | 36 |
6 files changed, 165 insertions, 6 deletions
@@ -1,3 +1,5 @@ +*_report.md + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/ExampleAssessments/CMP-4009B.yml b/ExampleAssessments/CMP-4009B.yml index 373f7f4..571c97f 100644 --- a/ExampleAssessments/CMP-4009B.yml +++ b/ExampleAssessments/CMP-4009B.yml @@ -17,8 +17,12 @@ files: - tester.py: functions: - dateTester(2) + printIfExecuted: False - turbine.py: classes: - Turbine: attributes: - - rating:int
\ No newline at end of file + - rating:int + - aNonExistantModule.py: + functions: + - aNonExistantFunction(1)
\ No newline at end of file diff --git a/ExampleAssessments/smol.yml b/ExampleAssessments/smol.yml new file mode 100644 index 0000000..d53e721 --- /dev/null +++ b/ExampleAssessments/smol.yml @@ -0,0 +1,21 @@ +files: + - pjtool.py: + classes: + - Date: + methods: + - __init__(4) + - __str__(1) + - __eq__(2) + - __lt__(2) + - numYears(2) + - numMonths(2) + - AnotherClass: + - floofleBerries(2) + tests: + - | + d1 = Date(2001, 8, 12) + d2 = Date(2001, 8, 12) + return d1 == d2 + - aNonExistantModule.py: + functions: + - aNonExistantFunction(1)
\ No newline at end of file @@ -1,6 +1,8 @@ +import reportWriter import argparse import tempfile import zipfile +import reflect import yaml import os @@ -10,11 +12,47 @@ def main(assessment_path, submission_path, student_no): with open(assessment_path, "r") as f: assessment_struct = yaml.safe_load(f) - print(assessment_struct) - - for required_file in assessment_struct["files"]: + reflection = reflect.Reflect(submission_path) + present_module_names = [i.name for i in reflection.client_modules] + writer = reportWriter.MarkDownReportWriter(student_no) + + for i, required_file in enumerate(assessment_struct["files"], 0): required_file = list(required_file.keys())[0] - print(required_file, required_file in os.listdir(submission_path)) + module_name = os.path.splitext(required_file)[0] + + if module_name not in present_module_names: + writer.append_module(module_name, False) + continue + + reflection.import_module(module_name) + writer.append_module(module_name, True, reflection.get_module_doc(module_name)) + + this_files_features = assessment_struct["files"][i][required_file] + if "classes" in this_files_features.keys(): + + present_classes = reflection.get_classes(module_name) + for j, class_name in enumerate(this_files_features["classes"], 0): + class_name = list(class_name.keys())[0] + + if class_name not in present_classes.keys(): + writer.append_class(class_name, False) + continue + + writer.append_class(class_name, True, present_classes[class_name][1]) + + present_methods = reflection.get_class_methods(module_name, class_name) + print(present_methods) + for required_method in this_files_features["classes"][j][class_name]["methods"]: + print(required_method) + + # print(submission_path) + # reflection = reflect.Reflect(submission_path) + # # reflection.import_module("pjtool") + # # print(reflection.get_classes("pjtool")) + # # print(reflection.get_class_methods("pjtool", "Date")["__eq__"]) + # reflection.import_module("tester") + # print(reflection.get_functions("tester")) + if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -15,17 +15,75 @@ class Reflect: self.client_modules = [p for p in pkgutil.iter_modules() if str(p[0])[12:-2] == self.client_code_path] def import_module(self, module_name): + """Imports a module. Before reflection can be conducted, a module + must be imported. WARNING: This will execute module code if it isn't + in a if __name__ == "__main__". Takes a module name (that the student made) + as the first argument. + + Args: + module_name (str): The name of a student's module to import + """ for module in self.client_modules: if module.name == module_name: self.imported_modules[module_name] = importlib.import_module(module.name) def get_module_doc(self, module_name): + """Gets the documentation provided for a module. + + Args: + module_name (str): The student's module name to get documentation for + + Returns: + str: Provided documentation + """ return inspect.getdoc(self.imported_modules[module_name]) + def get_classes(self, module_name): + """Gets the classes in a given module. The module must be imported first. + + Args: + module_name (str): The name of an imported module to get the name of. + + Returns: + dict: Dictionary of classes. The name of the class is the index, followed by + a tuple containing the class object and the classes' documentation. + """ + return { + i[0]: (i[1], inspect.getdoc(i[1])) + for i in inspect.getmembers(self.imported_modules[module_name]) + if inspect.isclass(i[1]) + } + + def get_class_methods(self, module_name, class_name): + """Gets the user generated methods of a given class. The module must be imported first. + + Args: + module_name (str): The name of the module in which the class is contained. + class_name (str): The name of the class. + + Returns: + dict: A dictionary of the methods. The index is the function name, followed by a tuple + containing the function object, the documentation, and a list of the named arguments. + WARNING: Does not deal with *args and **kwargs and stuff. + """ + return { + i[0]: (i[1], inspect.getdoc(i[1]), inspect.getfullargspec(i[1])[0]) + for i in inspect.getmembers( + self.get_classes(module_name)[class_name][0], + predicate=inspect.isfunction + ) + } + + def get_functions(self, module_name): + return { + i[0]: (i[1], inspect.getdoc(i[1]), inspect.getfullargspec(i[1])[0]) + for i in inspect.getmembers(self.imported_modules[module_name]) + if inspect.isfunction(i[1]) + } if __name__ == "__main__": user_code_path = "/media/veracrypt1/Nextcloud/UniStuff/3.0 - CMP 3rd Year Project/ExampleSubmissions/Submission_A" reflect = Reflect(user_code_path) reflect.import_module("pjtool") - print(reflect.get_module_doc("pjtool")) + print(reflect.get_class_methods("pjtool", "Date")) diff --git a/reportWriter.py b/reportWriter.py index e69de29..ebbfecf 100644 --- a/reportWriter.py +++ b/reportWriter.py @@ -0,0 +1,36 @@ +from dataclasses import dataclass +import datetime + +@dataclass +class MarkDownReportWriter: + student_no:str + + def __post_init__(self): + self.__push_line(""" +# %s Submission Report + +Report automatically generated at %s + +## Files\n\n""" % (self.student_no, datetime.datetime.now())) + + def __push_line(self, line): + with open("%s_report.md" % self.student_no, "a") as f: + f.write(line) + + def append_module(self, module_name, found = True, docs = None): + self.__push_line("### File: `%s.py`\n\n" % module_name) + if found: + self.__push_line(" - [x] Present\n") + if len(docs) > 2: + self.__push_line(" - [x] Documented (%d characters)\n\n" % (len(docs))) + else: + self.__push_line(" - [ ] Present\n\n") + + def append_class(self, class_name, found = True, docs = None): + self.__push_line("#### Class: `%s`\n\n" % class_name) + if found: + self.__push_line(" - [x] Present\n") + if len(docs) > 2: + self.__push_line(" - [x] Documented (%d characters)\n\n" % (len(docs))) + else: + self.__push_line(" - [ ] Present\n\n")
\ No newline at end of file |