diff --git a/config.ini b/config.ini new file mode 100644 index 0000000000000000000000000000000000000000..b15f3dedfc99572c2ba896d032554808a78a96f9 --- /dev/null +++ b/config.ini @@ -0,0 +1,9 @@ +[META] +author = Jan Maximilian Michal + + +[UPLAODER] +user = root +pass = homer +host = http://localhost:8000/ +rtoken = c13456ec3d71dc657e19fb826750f676 diff --git a/grim.py b/grim.py index 0c4e576e73b10bfc87295959f2eb052cfb6fba39..25c063d2625194e61b62a119bd9fe28d011245e2 100755 --- a/grim.py +++ b/grim.py @@ -1,6 +1,6 @@ #!/usr/local/bin/python3 -################################################################################ +########################################################################## # # This script contains the main part of hallgrim and is the only script that # needs to be invoked. The steps it takes to generate a task are as follows: @@ -15,12 +15,13 @@ # * a finisher compresses data if needed (needs to be implemented, maybe as # separate subparser). # -################################################################################ +########################################################################## import importlib import argparse import os import sys +import configparser # local import from hallgrim.IliasXMLCreator import packer @@ -29,18 +30,11 @@ from hallgrim.messages import * from hallgrim.parser import * from hallgrim.uploader import send_script -scaffolding = r''' -meta = {{ - 'author': '{}', - 'title': '{}', - 'type': '{}', - 'points': {}, -}} -task = """ decription """ -{} -feedback = """ decription """ -''' +def get_config(): + config = configparser.ConfigParser() + config.read('config.ini') + return config def file_to_module(name): @@ -93,7 +87,6 @@ def parseme(): "-a", "--author", help="Name of the scripts author", - default='ILIAS Author', metavar='AUTHOR' ) parser_new.add_argument( @@ -129,10 +122,6 @@ def parseme(): parser_gen = subparsers.add_parser( "upload", help="Subcommand to upload created xml instances.") - parser_gen.add_argument( - '--host', - help='The hostname of the ilias test implementation', - metavar='HOST') parser_gen.add_argument( 'script', help='The script that should be uploaded', @@ -152,6 +141,16 @@ def parseme(): def delegator(output, script_list, instances): + """ + It gets a list of filenames and delegates them to the correct handler. + Every file that does not end with .py will be ignored. Each script + is imported and then passed as module to the handler. + + Arguments: + output {filename} -- where to write the finished XML document + script_list {list} -- a list of filenames that contain scripts + instances {int} -- number of instances that should be generated + """ for script_name in filter(lambda a: a.endswith('.py'), script_list): script = importlib.import_module(file_to_module(script_name)) handler = { @@ -166,6 +165,18 @@ def delegator(output, script_list, instances): def handle_gap_questions(output, script, instances): + """ Handles gap questions of all kinds + + A script can contain any mixture of gap, numeric gap and choice gap + questions. The data object that is needed by the XML creating scripts + is generated and the task itself is handled by the parser. The parser + returns the intermediate representation of the task. + + Arguments: + output {str} -- where to write the final file + script {module} -- the loaded module that describes the task + instances {int} -- number of instances that should be generated + """ script_is_valid(script, required=['meta', 'task', 'feedback']) data = { 'type': type_selector(script.meta['type']), @@ -186,6 +197,16 @@ def handle_gap_questions(output, script, instances): def handle_choice_questions(output, script, instances): + """ + Handles multiple and single choice questions. The relevant parts of the + script are fed into a parser that return the correct intermediate + representation for the task. In this case a list of answers. + + Arguments: + output {str} -- where to write the finished XML document + script {module} -- the loaded module that describes the task + instances {int} -- number of instances that should be generated + """ script_is_valid(script, required=['meta', 'task', 'choices', 'feedback']) data = { 'type': type_selector(script.meta['type']), @@ -207,6 +228,25 @@ def handle_choice_questions(output, script, instances): def handle_new_script(name, qtype, author, points): + """ Creates a new script file. + + Takes in some meta information from the command line of if not present takes + it from the config.ini or uses default values. + + TODO: put the configuration before the parser and use as default values + + Arguments: + name {str} -- name of the script, will also become filename + qtype {str} -- question type (choice, gap, alignment) + author {str} -- the author of the script + points {float} -- number of points for the task + """ + from hallgrim.templates import scaffolding + config = get_config() + + if not author: + author = config['META']['author'] + with open('scripts/' + name + '.py', 'w') as new_script: choice = '' if qtype in ['multiple choice', 'single choice']: @@ -216,9 +256,27 @@ def handle_new_script(name, qtype, author, points): author, name, qtype, points, choice).strip(), file=new_script) info('Generated new script "{}."'.format(new_script.name)) -def handle_upload(script_path, hostname): - r = send_script(script_path) - info("Uploaded %s. Status code looks %s." % (script_path, "good" if r else "bad")) + +def handle_upload(script_path): + """ Passes data to the upload script. + + The status code should be 500, since ILIAS always replies with that error + code after an upload is confirmed. If anything else the script will say + the status code was bad. + + Arguments: + script_path {str} -- path to the file that should be uploaded + """ + config = get_config() + r = send_script( + script_path, + config['UPLAODER']['host'], + config['UPLAODER']['user'], + config['UPLAODER']['pass'], + config['UPLAODER']['rtoken'], + ) + info("Uploaded %s. Status code looks %s." % + (script_path, "good" if r else "bad")) if __name__ == '__main__': markdown = get_markdown() diff --git a/hallgrim/IliasXMLCreator/gap.py b/hallgrim/IliasXMLCreator/gap.py index 010a2fb709f076d7e9089eed916e1d686cf82d15..71b5923df326595fdf5ba816bb09dcad6e390955 100644 --- a/hallgrim/IliasXMLCreator/gap.py +++ b/hallgrim/IliasXMLCreator/gap.py @@ -10,15 +10,14 @@ def xml_print(element, **kwargs): ### -# Solution 1 -# ['text', 'text', 'text'] -# [('gap_solution', points), ..., (['one', 'two', 'three'], points)] # +# Type of first item in gap tuple determines the gap type. gap might be changed +# to set # str : str_gap # list : choice_gap # tuple : num_gap (x, min, max) # -# Solution 2 +# Format of gap_list # ['text', ('gap_solution', points), ('gap_solution', points), 'text', ...] # ### diff --git a/hallgrim/templates.py b/hallgrim/templates.py new file mode 100644 index 0000000000000000000000000000000000000000..d17f246a246a73a68a9adbe69c8f624b7a3594e5 --- /dev/null +++ b/hallgrim/templates.py @@ -0,0 +1,12 @@ +scaffolding = r''' +meta = {{ + 'author': '{}', + 'title': '{}', + 'type': '{}', + 'points': {}, +}} + +task = """ decription """ +{} +feedback = """ decription """ +''' \ No newline at end of file diff --git a/hallgrim/uploader.py b/hallgrim/uploader.py index 3abd403ca54cacc8ec983c91656ecaf15dfcc8be..0cfca4c55ada94b2ef6c5daf33946b5ce2f683ac 100644 --- a/hallgrim/uploader.py +++ b/hallgrim/uploader.py @@ -1,4 +1,4 @@ -################################################################################ +########################################################################## # # This is the uploader. It simplifies the testing process immensely and makes # autoILIAS finally obsolete, since this system uses a proper Ilias @@ -13,34 +13,34 @@ # # Sadly this script adds some ugly dependencies like requests_toolbelt. # -################################################################################ +########################################################################## import os import requests from requests_toolbelt import MultipartEncoder -from lxml import html __all__ = ['send_script'] # static data -host = "http://localhost:8000/" -login = {"username" : "root", "password" : "homer", "cmd[showLogin]" : "Login"} -upload_url = host + "ilias/ilias.php?ref_id=65&cmd=post&cmdClass=ilobjquestionpoolgui&cmdNode=26:gb&baseClass=ilRepositoryGUI&fallbackCmd=upload&rtoken=c13456ec3d71dc657e19fb826750f676" -import_url = host + "ilias/ilias.php?ref_id=65&cmd=post&cmdClass=ilobjquestionpoolgui&cmdNode=26:gb&baseClass=ilRepositoryGUI&fallbackCmd=questions&rtoken=c13456ec3d71dc657e19fb826750f676" -confirm_url = host + "ilias/ilias.php?ref_id=65&cmd=post&cmdClass=ilobjquestionpoolgui&cmdNode=26:gb&baseClass=ilRepositoryGUI&rtoken=c13456ec3d71dc657e19fb826750f676" +login_url = "ilias/login.php" +upload_url = "ilias/ilias.php?ref_id=67&cmd=post&cmdClass=ilobjquestionpoolgui&cmdNode=26:gb&baseClass=ilRepositoryGUI&fallbackCmd=upload&rtoken=%s" +import_url = "ilias/ilias.php?ref_id=67&cmd=post&cmdClass=ilobjquestionpoolgui&cmdNode=26:gb&baseClass=ilRepositoryGUI&fallbackCmd=questions&rtoken=%s" +confirm_url = "ilias/ilias.php?ref_id=67&cmd=post&cmdClass=ilobjquestionpoolgui&cmdNode=26:gb&baseClass=ilRepositoryGUI&rtoken=%s" import_data = { - "cmd[importQuestions]" : "Import", + "cmd[importQuestions]": "Import", } confirm_data = { - "cmd[importVerifiedFile]" : "Import", - "questions_only" : "1", + "cmd[importVerifiedFile]": "Import", + "questions_only": "1", } -def send_script(filepath): +def send_script(filepath, host, user, password, rtoken): + login = {"username": user, "password": password, "cmd[showLogin]": "Login"} + file = MultipartEncoder(fields={ 'xmldoc': ( os.path.basename(filepath), @@ -48,13 +48,13 @@ def send_script(filepath): 'text/xml' ) }) + header = {'Content-Type': file.content_type} # session create and login session = requests.Session() - r = session.post(host + "ilias/login.php", data=login) - r = session.post(import_url, data=import_data) - r = session.post(upload_url, data=file, headers={'Content-Type': file.content_type}) - r = session.post(confirm_url, data=confirm_data) + r = session.post(host + login_url, data=login) + r = session.post(host + (import_url % rtoken), data=import_data) + r = session.post(host + (upload_url % rtoken), data=file, headers=header) + r = session.post(host + (confirm_url % rtoken), data=confirm_data) return r.status_code == 500 - diff --git a/scripts/Vermischtes aus Kapitel 3 (I1-ID: uo93wey0oog0).py b/scripts/Vermischtes aus Kapitel 3 (I1-ID: uo93wey0oog0).py index 0316f0da01d0e5a1f7d8d0e72cd809ad690738ed..68cd72145e3e60b07a5a054c069fc2436362995a 100644 --- a/scripts/Vermischtes aus Kapitel 3 (I1-ID: uo93wey0oog0).py +++ b/scripts/Vermischtes aus Kapitel 3 (I1-ID: uo93wey0oog0).py @@ -21,12 +21,12 @@ choices = """ feedback = """ | Aussage | Wert | Begründung | |---------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Formalparameter sind lokale Variablen. | TRUE | Die im Methodenkopf definierten Parameter heißen auch Formalparameter und werden innerhalb der Methode als Variable gebraucht. Tatsächliche Parameter sind die im Programmablauf übergebenen Werte oder Referenzen. | +| Formalparameter sind lokale Variablen. | TRUE | Die im Methodenkopf definierten Parameter heißen auch Formalparameter und werden innerhalb der Methode als Variable gebraucht. Tatsächliche Parameter sind die im Programmablauf übergebenen Werte oder Referenzen. | | Die JVM interpretiert Java-Bytecode. | TRUE | Java-Bytecode ist das Instruction-Set der Java-Virtual-Machine | | (false ? true : false) == (true ? false : true) | TRUE | (false ? true : false) == (true ? false : true) -> false == false -> true | | Das rekursive Grundschema lautet: Rekurriere - Finalisiere (falls trivial) | FALSE | Das rekursive Grundschema lautet: Finalisiere (falls trivial) - Rekurriere | -| Die Java-Laufzeitumgebung beinhaltet Compiler, Klassenbibliotheken und die (JVM) Java Virtual Machine. | FALSE | Wikipedia: "Allgemein besteht die Laufzeitumgebung aus der Java Virtual Machine (Java VM), die für die Ausführung der Java-Anwendungen verantwortlich ist, einer Programmierschnittstelle (API, für Application and Programming Interface) und weiteren Programmbibliotheken." Ein Compiler gehört nicht dazu. | -| An Ausdrücke dürfen keine Zuweisungen erfolgen. | FALSE | Da zum Beispiel x[0] = 1 | +| Die Java-Laufzeitumgebung beinhaltet Compiler, Klassenbibliotheken und die (JVM) Java Virtual Machine. | FALSE | Wikipedia: "Allgemein besteht die Laufzeitumgebung aus der Java Virtual Machine (Java VM), die für die Ausführung der Java-Anwendungen verantwortlich ist, einer Programmierschnittstelle (API, für Application and Programming Interface) und weiteren Programmbibliotheken." Ein Compiler gehört nicht dazu. | +| An Ausdrücke dürfen keine Zuweisungen erfolgen. | FALSE | Da zum Beispiel x[0] = 1 | | Methoden mit Signatur int (int) werden mit call-by-reference aufgerufen. | FALSE | Sämtliche Java Methoden werden mit call-by-value aufgerufen. Eine gute Erklärung zu den Unterschieden finden Sie hier. | | (false ? false : true) == (false ? true : false) | FALSE | (false ? false : true) == (false ? true : false) -> true == false -> false | """