Commit 8179bdf2 authored by David Paul's avatar David Paul
Browse files

Initial implementation

parent 8f410337
from automarker_server import *
from browser import alert
from browser import document
from browser import html
from browser import window
jq = window.jQuery.noConflict(True)
window.jq = jq;
editor = window.editor
def echo_prompt(prompt = ""):
"""Function to make prompt to input appear properly"""
print(prompt, end = "")
value = input(prompt)
print(value, end="\n")
return value
class Output:
"""Writes output to #output"""
def write(self, *args):
for arg in args:
jq("#output").val(jq("#output").val() + arg)
def display_exercise(exercise):
"""Displays the given exercise"""
window.exercise.html = exercise.title
window.instructions.html = exercise.instructions
window.editor.setValue(exercise.code, -1)
window.tests.clear()
for test_id in range(len(exercise.tests)):
test = exercise.tests[test_id]
id = "test_%d" % test_id
classname = "list-group-item list-group-item-danger"
list_element = html.A(Class = classname, id = id)
list_element <= test.name
list_element.bind("click", show_test)
list_element.bind("dblclick", run_test)
window.tests <= list_element
jq("#output-row").addClass("hidden")
jq("#test-row").addClass("hidden")
def update_tests(remove_active = False):
"""Checks if all tests have passed, removing the active class if requested"""
all_passed = True
jq("#grade").addClass("disabled")
for test_id in range(len(exercise.tests)):
test = exercise.tests[test_id]
jq("#test_%d" % test_id).addClass("list-group-item-danger")
jq("#test_%d" % test_id).removeClass("list-group-item-success")
if remove_active:
jq("#test_%d" % test_id).removeClass("active")
if test.expected_output == test.actual_output:
jq("#test_%d" % test_id).addClass("list-group-item-success")
jq("#test_%d" % test_id).removeClass("list-group-item-danger")
else:
all_passed = False
if all_passed:
jq("#grade").removeClass("disabled")
def display_test(id):
"""Displays the test with the given id"""
update_tests(True)
jq("#output-row").addClass("hidden")
jq("#test-row").removeClass("hidden")
jq("#test_%d" % id).addClass("active")
test = exercise.tests[id]
jq("#expected").val(test.expected_output)
jq("#actual").val(test.actual_output)
jq("#input").val(test.input)
def show_test(ev):
"""Displays the test that was selected"""
id = int(ev.target.id[len("test_"):])
display_test(id)
def run_test(ev):
"""Runs the test that was selected"""
id = int(ev.target.id[len("test_"):])
execute_test(exercise.tests[id])
display_test(id)
def get_code():
"""Gets the code currently in the editor"""
return window.editor.getValue()
def display_checks(code):
"""Displays alerts for any check that fails - returns True if all checks pass, False otherwise"""
for check in exercise.checks:
if not check.check(code):
alert(check.text)
return False
return True
def execute_test(test):
"""Runs the code to complete a test"""
code = get_code()
if not display_checks(code):
return
test.run(code, echo_prompt)
update_tests()
def execute_run(*args):
"""Runs the code for the user to interact with"""
jq("#test-row").addClass("hidden")
jq("#output-row").removeClass("hidden")
jq("#output").val("")
code = get_code()
execute(code, output, output, sys.stdin, echo_prompt)
def execute_tests(*args):
"""Executes each test for the exercise"""
code = get_code()
if not display_checks(code):
return
exercise.run(code, echo_prompt)
display_test(0)
def grade_code():
if jq("#grade").hasClass("disabled"):
alert("Run all tests successfully to allow grade upload")
else:
window.submitGrade();
def code_change(*args):
"""Handles any time the code changes - test results become invalid"""
jq("#output-row").addClass("hidden")
jq("#test-row").addClass("hidden")
for test in exercise.tests:
test.actual_output = ""
update_tests(True)
output = Output()
exercise = get_exercise(window.exercise_id)
window.exercise.html = "No Exercise Specified"
window.instructions.html = "No exercise was specified, or the exercise is unavailable. Please contact your instructor."
display_exercise(exercise)
document["run"].bind("click", execute_run)
document["test-all"].bind("click", execute_tests)
document["grade"].bind("click", grade_code)
editor.on("input", code_change)
from exercise import *
from exercises import *
import sys
def get_exercise(id = 0):
"""Loads the exercise to be completed"""
try:
id = int(id)
except ValueError:
id = 0
if id < 0 or id >= len(exercises):
id = 0
return exercises[id]
original_input = input
def output_input(prompt = ""):
"""A function to output the value that is input before it is returned."""
value = original_input(prompt)
print(value)
return value
if __name__ == "__main__":
exercise_id = sys.argv[-2]
exercise = get_exercise(exercise_id)
file = sys.argv[-1]
fin = open(file)
code = fin.read()
if not exercise.run(code, output_input):
for test in exercise.tests:
if test.expected_output != test.actual_output:
print("Expected:", test.expected_output)
print("Actual:", test.actual_output)
sys.exit(1) # Failed
{
"name": "python-automarker",
"authors": [
"David Paul <david@davidjohnpaul.com>"
],
"description": "A Python 3 AutoMarker",
"main": "",
"keywords": [
"python",
"automarker"
],
"license": "MIT",
"homepage": "https://bitbucket.org/davidjohnpaul/automarker",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"bootstrap": "^3.3.6",
"ace": "git://github.com/ajaxorg/ace-builds.git#^1.2.3",
"pythonauto": "https://github.com/csev/pythonauto.git",
"html5shiv": "^3.7.3",
"Brython3.2.7-20160621-184325.tar": "https://github.com/brython-dev/brython/releases/download/3.2.7/Brython3.2.7-20160621-184325.tar.gz"
}
}
#nograde {
display: none;
color: red;
}
import re
import sys
import traceback
# Pattern to remove Traceback from before an exec call
error_pattern = ".*?\$exec_\d+.*? "
regex_error = re.compile(error_pattern, re.DOTALL)
def execute(code, stdout = sys.stdout, stderr = sys.stderr, stdin = sys.stdin, input = input):
"""Executes the given code, with standard output and standard error going to the given values"""
old_stdout = sys.stdout
old_stderr = sys.stderr
old_stdin = sys.stdin
sys.stdout = stdout
sys.stderr = stderr
sys.stdin = stdin
try:
available_vars = {}
available_vars["input"] = input
exec(code, available_vars)
return True
except Exception as exc:
msg = traceback.format_exc()
msg = regex_error.sub("", msg, 1)
print(msg)
return False
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
sys.stdin = old_stdin
class Test:
"""A test to run over the given code"""
def __init__(self, name, expected_output = "", input = ""):
"""Gives the test the give name, expected output, and input that it will provide when run"""
self.name = name
self.expected_output = expected_output
self.actual_output = ""
self.input = input
self.remaining_input = ""
def write(self, *args):
"""Writes the output to the test object"""
for arg in args:
self.actual_output += arg
def read(self, *args):
"""Reads the input from the test object"""
if len(self.remaining_input) <= 0:
return "\n"
ret_val = self.remaining_input[0]
self.remaining_input = self.remaining_input[1:]
return ret_val
def readline(self, *args):
"""Reads the input from the test object"""
remaining = self.remaining_input.partition("\n")
self.remaining_input = remaining[2]
return remaining[0]
def close(self, *args):
"""Allow Test to be stdin"""
pass
def run(self, code, input = input):
"""Executes this test, returning True if the test passes (False otherwise)"""
self.remaining_input = self.input
self.actual_output = ""
execute(code, self, self, self, input)
return self.actual_output == self.expected_output
class Check:
"""Performs a check on the code"""
def __init__(self, text, regex, ensure_no_match = False):
"""Sets the text and the pattern to match"""
self.text = text
self.regex = re.compile(regex)
self.ensure_no_match = ensure_no_match
def check(self, code):
"""Ensures this check succeeds"""
match = self.regex.search(code)
if match == None:
return self.ensure_no_match
return not self.ensure_no_match
class Exercise:
"""The exercise to be completed"""
def __init__(self, title, instructions = "", code = "", tests = [], checks = []):
"""Sets the title, instructions, initial code, tests, and checks to be performed to complete this exercise"""
self.title = title
self.instructions = instructions
self.code = code
self.tests = tests
self.checks = checks
def run_checks(self, code):
"""Ensures all checks pass on the given code, returning True if all checks succeed (False otherwise)"""
all_succeeded = True
for check in self.checks:
all_succeeded = check.check(code) and all_succeeded
return all_succeeded
def run_tests(self, code, input = input):
"""Runs all tests in this exercise, returning True if all succeed (False otherwise)"""
all_succeeded = True
for test in self.tests:
all_succeeded = test.run(code, input) and all_succeeded
return all_succeeded
def run(self, code, input = input):
"""Runs all checks and, if they succeed, all tests, returning True if all checks and tests succeed (False otherwise)"""
return self.run_checks(code) and self.run_tests(code, input)
from exercise import *
exercises = []
exercise = Exercise("Error", "Invalid exercise", "", [], [Check("Invalid", ""), Check("Invalid", "", True)])
exercises.append(exercise)
# Exercise 1
exercise = Exercise("Hello World!")
exercise.instructions = "This first exercise is designed to introduce you to the Python Automarker tool.<br/>This area will give you instructions, specifying the exercise you need to complete.<br/>Below is a text area where you can type your code to solve the problem. It is recommended you write and test the code on your system before copying it here to complete the exercise.<br/>Below the code area are three buttons: <ul><li><em>Run</em>: Allows you to run your code to test it is working correctly. You will be prompted to enter any input, and output will be shown below.</li><li><em>Run tests</em>: Runs all of the tests associated with the given exercise (the tests are listed in the Tests area of the page). Input will be taken from the test, and your code's output must match the test's expected output to pass. You can click on an individual test to see its details. You can also run a single test by double-clicking it.</li><li><em>Grade</em>: Once all tests have passed, you can submit your work using this button to have your grade recorded for the exercise.</li></ul>If there is an error in your code, an error message will display in the output area. If a test has failed, click on the test in the list to see that test's output.<br/>Familiarise yourself with the tool. For this exercise, the code you require is already provided (there is no need for you to understand it yet), so ensure you can correctly submit your grade after running all the tests successfully."
exercise.code = "name = input(\"Enter your name: \")\nprint(\"Hello\", name)"
exercise.tests = [Test("Greet Alice", "Enter your name: Alice\nHello Alice\n", "Alice"), Test("Greet Bob", "Enter your name: Bob\nHello Bob\n", "Bob"), Test("Greet Carol", "Enter your name: Carol\nHello Carol\n", "Carol")]
exercise.checks = [Check("Your code requires a print statement", "print")]
exercises.append(exercise)
# Exercise 2
exercise = Exercise("Age Calculator")
exercise.instructions = "Write a program that asks the user to enter their age (in years). If the user is 100 or older, tell them \"You've already turned 100!\". Otherwise, if they are less than 0, tell them \"Try again after you are born!\". If neither of these cases apply, calculate the number of years before they turn 100 and output the message \"You will be 100 in x years!\" (where x is replaced by the number of years before they turn 100). You must use an if...elif...else statement for this problem."
exercise.code = "age = input(\"Enter your current age in years: \")"
exercise.tests = [Test("Over 100", "Enter your current age in years: 101\nYou've already turned 100!\n", "101"),
Test("Exactly 100", "Enter your current age in years: 100\nYou've already turned 100!\n", "100"),
Test("Not yet born", "Enter your current age in years: -1\nTry again after you are born!\n", "-1"),
Test("18 year old", "Enter your current age in years: 18\nYou will be 100 in 82 years!\n", "18"),
Test("52 year old", "Enter your current age in years: 52\nYou will be 100 in 48 years!\n", "52")]
exercise.checks = [Check("You need to use an if...elif...else statement for this problem", "if"),
Check("You need to use an if...elif...else statement for this problem", "elif"),
Check("You need to use an if...elif...else statement for this problem", "else"),
Check("You need to convert the input to an integer", "\\sint\\(")]
exercises.append(exercise)
# Exercise 3
exercise = Exercise("Right Justify")
exercise.instructions = "Write a function named <em>right_justify</em> that takes a string named <em>input_string</em> as a parameter. If the input string is longer than 79 characters, the string should be printed as is, Otherwise, the function should print the string with enough leading spaces that the last letter of the string is in column 80 of the display. (Note: Python has an inbuilt function called len that returns the length of a string. You can use this function to help perform the task)."
exercise.code = "def right_justify(input_string):\n\tpass # TODO: Complete this function\n\nvalue = input(\"Enter string to justify: \")\nright_justify(value)"
exercise.tests = [Test("Short string", "Enter string to justify: hello\n hello\n", "hello"),
Test("Long string", "Enter string to justify: This is a longer string that does not actually need to be justified. It is already long enough.\nThis is a longer string that does not actually need to be justified. It is already long enough.\n", "This is a longer string that does not actually need to be justified. It is already long enough."),
Test("Under limit string", "Enter string to justify: This string is exactly 79 characters long & so does require some justification.\n This string is exactly 79 characters long & so does require some justification.\n", "This string is exactly 79 characters long & so does require some justification."),
Test("Over limit string", "Enter string to justify: This string is exactly 80 characters long and so does not require justification.\nThis string is exactly 80 characters long and so does not require justification.\n", "This string is exactly 80 characters long and so does not require justification.")]
exercise.checks = [Check("You should use len to check the length of the string", "len\("),
Check("You need a function named right_justify that takes input_string as a parameter", "def right_justify\\(input_string\\)")]
exercises.append(exercise)
# Exercise 4
exercise = Exercise("Paperweight")
exercise.instructions = "Create a dictionary named <em>weight</em>, initialised with the following values:<ul><li>\"pencil\": 10</li><li>\"pen\": 20</li><li>\"paper\": 4</li><li>\"eraser\": 80</li></ul>Create another dictionary named <em>available</em>, initialised with the following values:<ul><li>\"pen\": 3</li><li>\"pencil\": 5</li><li>\"eraser\": 2</li><li>\"paper\": 10</li></ul>If the <em>weight</em> dictionary gives the weight of an individual item of each type, and the <em>available</em> dictionary specifies the number of each item that is available, write code that determines the total weight of all available items (i.e. what is the overall weight of all the pens, pencils, paper, and erasers?)"
exercise.code = "weight = {} #TODO: Initialise weight with correct values\navailable = {} # TODO: Initialise available with correct values\n\noverall_weight = 0\n# TODO: Loop through all available items to determine their overall weight\nprint(\"Overall weight:\", overall_weight)"
exercise.tests = [Test("Weight calculation", "Overall weight: 310\n")]
exercise.checks = [Check("You should use a for loop to loop over all keys in one of the dictionaries", "for "),
Check("You should calculate the value", "310", True),
Check("You need to specify the correct weights in a dictionary", "\"pencil\":(\\s)+10"),
Check("You need to specify the correct weights in a dictionary", "\"pen\":(\\s)+20"),
Check("You need to specify the correct weights in a dictionary", "\"paper\":(\\s)+4"),
Check("You need to specify the correct weights in a dictionary", "\"eraser\":(\\s)+80"),
Check("You need to specify the correct availability in a dictionary", "\"pen\":(\\s)+3"),
Check("You need to specify the correct availability in a dictionary", "\"pencil\":(\\s)+5"),
Check("You need to specify the correct availability in a dictionary", "\"eraser\":(\\s)+2"),
Check("You need to specify the correct availability in a dictionary", "\"paper\":(\\s)+10")]
exercises.append(exercise)
# Exercise 5
exercise = Exercise("Bug Hunting")
exercise.instructions = "Debug the following program:"
exercise.code = "Def factorial(n):\n \"\"\"Calculate the factorial of the given value and return the result.\n\n The factorial of n is the product of all positive integers less than or equal to n.\n\n Keyword arguments:\n n -- A positive integer\n \"\"\"\n result = 1\n while n != 0:\n n = n - 1\n result = result * n\n return result\n\n# Calculate factorial for the first four integers\nfor i in range(-1, 5):\n print('Factorial of', i, 'is', factorial(i)"
exercise.tests = [Test("Expected output", "Factorial of 1 is 1\nFactorial of 2 is 2\nFactorial of 3 is 6\nFactorial of 4 is 24\n")]
exercise.checks = [Check("Ensure function begins with \"def\" (not \"Def\")", "def factorial"),
Check("Should you be testing for inequality only?", "!=", True),
Check("We only want the factorial for integers 1, 2, 3, and 4", "range\(-1", True)]
exercises.append(exercise)
# Exercise 6
exercise = Exercise("Palindromes")
exercise.instructions = "<p>A <a href=\"https://en.wikipedia.org/wiki/Palindrome\">palindrome</a> is a sequence of characters which reads the same backward or forward. For example, \"anna\" is a palindrome because reversing the letters gives \"anna\", which is the same as when the letters are not reversed.</p><p>Write a recursive function called <em>is_palindrome</em> that takes a string named <em>word</em> as its parameter and returns True if <em>word</em> has length less than or equal to 1. If the length of word is greater than 1, the function should return False if the first character of <em>word</em> is different to the last character of <em>word</em>. If the length of <em>word</em> is greater than 1, and the first and last characters of the string are identical, the function should return the result of <em>is_palindrome()</em> with the parameter of <em>word</em> with its first and last characters removed (e.g. <em>is_palindrome(\"anna\")</em> should return the result of <em>is_palindrome(\"nn\")</em>).</p>"
exercise.code = "def is_palindrome(word):\n\tpass # TODO: Implement this function\n\npossible_palindrome = input(\"Enter a word/phrase to check: \")\nif is_palindrome(possible_palindrome):\n\tprint(possible_palindrome, \"is a palindrome\")\nelse:\n\tprint(possible_palindrome, \"is not a palindrome\")"
exercise.tests = [Test("a", "Enter a word/phrase to check: a\na is a palindrome\n", "a"),
Test("anna", "Enter a word/phrase to check: anna\nanna is a palindrome\n", "anna"),
Test("banana", "Enter a word/phrase to check: banana\nbanana is not a palindrome\n", "banana"),
Test("racecar", "Enter a word/phrase to check: racecar\nracecar is a palindrome\n", "racecar"),
Test("Anna", "Enter a word/phrase to check: Anna\nAnna is not a palindrome\n", "Anna")]
exercise.checks = [Check("You should use len to check the length of the string", "len\\("),
Check("You need to make a recursive call to is_palindrome", "return(\\s)+is_palindrome\\("),
Check("You need a base case in your recursive function", "return(\\s)+[^i][^s]")]
exercises.append(exercise)
# Exercise 7
exercise = Exercise("Turtle Power!")
exercise.instructions = "Read and work through Chapter 4 of the textbook Think Python 2nd Edition.<br />Note: You are probably better off doing this task outside of the browser (e.g. the python3 enviroment on turing) - you can't rerun your code here without refreshing. However, you can try things here if you like - and don't forget to submit something for grading!"
exercise.code = "import turtle\n\ndef square(turtle):\n for i in range(4):\n turtle.fd(100)\n turtle.lt(90)\n\nbob = turtle.Turtle()\nsquare(bob)\n\n# In the browser, you need to use the following rather than turtle.mainloop()\nturtle._Screen().end()"
exercise.tests = [] # Any tests/checks in here don't work with server-side checking
exercise.checks = []
exercises.append(exercise)
# Exercise 8
exercise = Exercise("My Kingdom For A Horse!")
exercise.instructions = "A farrier was contacted one Sunday and asked to shoe a horse. Because Sunday was his day off, the farrier advised the rider to come back tomorrow, and that it would cost $150. But the rider kept insisting that the horse needed to be shod immediately, until eventually the farrier wore down and made the following offer: he would shoe the horse for free, but the rider needed to buy the nails from him and would be charged $0.01 for the first nail, and twice the price of the previous nail for each nail after that (e.g. the second nail would cost $0.02, the third $0.04, and so on). Given that a horse needs four shoes, and each shoe needs six nails, complete the following program to determine the total cost for the rider. It just goes to show how quickly things can grow with exponential growth!"
exercise.code = "total_cost = 0\ncost_of_nail = 1\nfor horse_foot in range(4):\n for nail in range(6):\n # TODO: Add price of current nail to total_cost and determine the cost of the next nail\n pass\n\nprint(\"The total cost to shoe the horse would be ${0:.2f}\".format(total_cost / 100))"
exercise.tests = [Test("Cost Calculator", "The total cost to shoe the horse would be $167772.15\n")]
exercise.checks = [Check("You should perform the calculation rather than hard-coding the value", "167772", True)]
exercises.append(exercise)
# Exercise 9
exercise = Exercise("Viewing People As Objects")
exercise.instructions = "Create a class called <code>Person</code> with the following attributes:<ul><li>name - A string representing the person's name</li></li>age - An int representing the person's age in years</li></ul>As well as appropriate <code>__init__</code> and <code>__str__</code> methods, include the following methods:<ul><li><code>get_name(self)</code> - returns the name of the person</li><li><code>get_age(self)</code> - returns the age of the person</li><li><code>set_name(self, new_name)</code> - sets the name for the person</li><li><code>set_age(self, new_age)</code> - sets the age for the person</li><li><code>is_older_than(self, other)</code> - returns True if this person is older than the one passed as the other parameter, False otherwise"
exercise.code = "class Person:\n # TODO: Implement this class properly\n def __init__(self, name, age):\n pass\n\n def __str__(self):\n pass\n\n def get_name(self):\n pass\n\n def get_age(self):\n pass\n\n def set_name(self, new_name):\n pass\n\n def set_age(self, new_age):\n pass\n\n def is_older_than(self, other):\n pass\n\nalice = Person(\"Alice\", 18)\nbob = Person(\"Bob\", 19)\nif bob.is_older_than(alice):\n print(\"{0} is older than {1}\".format(bob.get_name(), alice.get_name()))\nelse:\n print(\"{0} is older than {1}\".format(alice.get_name(), bob.get_name()))"
exercise.tests = [Test("Alice and Bob", "Bob is older than Alice\n")]
exercise.checks = [Check("You should calculate the output", "Bob is older than Alice", True),
Check("To edit the name for an instance of the Person class, you need to set self.name", "self\.name(\\s)+="),
Check("To edit the age for an instance of the Person class, you need to set self.age", "self.age(\\s)+=")]
exercises.append(exercise)
# Exercise 10
exercise = Exercise("Test-Driven Development")
exercise.instructions = "An online store requires a Python function to calculate the price of orders. The function should be called <code>calculate_price</code> and take the following two arguments (in order):<ul><li><code>price</code>: A dictionary mapping the name of each item sold by the store to the price of the item (in dollars)</li><li><code>order</code>: A dictionary mapping the name of each item a customer wishes to buy to the quantity of that item they wish to purchase</li></ul>The function should iterate over each item in the order and add the price to buy the desired quantity of that item to the total cost of the order. If any item in the order does not have a corresponding price, your function should raise a <code>KeyError</code>.<p>Once the cost of each item in the order has been calculated, the function should test to see if the overall cost for all items is greater than $100. If it is, a 10% discount should be applied to the overall cost. Otherwise, if the order is greater than $50, a 5% discount should be applied.</p><p>The overall cost of the order (after any applicable discounts) should then be returned by the function.</p><p>Write a function to meet these requirements.</p>"
exercise.code = "def calculate_price(price, order):\n\t# TODO: Implement this\n\tpass\n\nprice = {'book': 10.0, 'magazine': 5.5, 'newspaper': 2.0}\n\norder1 = {'book': 10}\norder2 = {'book': 1, 'magazine': 3}\norder3 = {'magazine': 5, 'book': 10}\n\nassert(95 == calculate_price(price, order1))\nassert(26.5 == calculate_price(price, order2))\nassert(114.75 == calculate_price(price, order3))"
exercise.tests = []
exercise.checks = [Check("Please don't alter the assertions", "assert\\(95 == calculate_price\\(price, order1\\)\\)"),
Check("Please don't alter the assertions", "assert\\(26.5 == calculate_price\\(price, order2\\)\\)"),
Check("Please don't alter the assertions", "assert\\(114.75 == calculate_price\\(price, order3\\)\\)"),
Check("Please don't alter the orders", "order1 = \\{'book': 10\\}"),
Check("Please don't alter the orders", "order2 = \\{'book': 1, 'magazine': 3\\}"),
Check("Please don't alter the orders", "order3 = \\{'magazine': 5, 'book': 10\\}"),
Check("Please don't alter the prices", "price = \\{'book': 10.0, 'magazine': 5.5, 'newspaper': 2.0\\}")]
exercises.append(exercise)
<?php
# Based on https://github.com/csev/pythonauto/blob/master/grade.php
require_once('bower_components/pythonauto/util/lti_util.php');
session_start();
if (isset($_REQUEST["exercise"]) && preg_match("/^\d+$/", $_REQUEST["exercise"])) {
$exercise = $_REQUEST["exercise"];
} else {
echo json_encode(Array("status" => "failure", "detail" => "No exercise in request"));
return;
}
if (isset($_REQUEST["code"])) {
$code = $_REQUEST["code"];
$grade = "1.0";
} else {
$grade = "0.0";
}
$context = new BLTI(false, true, false);
if (!$context->valid) {
echo json_encode(Array("status" => "failure", "detail" => "No context in session"));
return;
}
$endpoint = $context->getOutcomeService();
if ($endpoint === false) {
echo json_encode(Array("status" => "failure", "detail" => "No grade service available"));
return;
}
$sourcedid = $context->getOutcomeSourceDID();
# Hack to work with UNE's LMS
$sourcedid = str_replace("\\\"", "\"", $sourcedid);
if ($sourcedid === false) {
echo json_encode(Array("status" => "failure", "detail" => "No grade entry available"));
return;
}
if (isset($_SESSION['oauth_consumer_key']) && isset($_SESSION['oauth_consumer_secret'])
&& $_SESSION['oauth_consumer_key'] !== '' && $_SESSION['oauth_consumer_secret'] !== '') {
$oauth_consumer_key = $_SESSION['oauth_consumer_key'];
$oauth_consumer_secret = $_SESSION['oauth_consumer_secret'];
} else {
echo json_encode(Array("status" => "failure", "detail" => "No key/secret in session"));
return;
}
function execAvailable() {
# Function based on http://stackoverflow.com/questions/3938120/check-if-exec-is-disabled#answer-12980534
$exec_available = true;
if (ini_get('safe_mode')) {
$exec_available = false;
} else {
$d = ini_get('disable_functions');
$s = ini_get('suhosin.executor.func.blacklist');
if ("$d$s") {
$array = preg_split('/,\s*/', "$d,$s");
if (in_array('exec', $array)) {
$exec_available = false;
}
}
}
return $exec_available;
}
# Ensure code passes tests on server
if (isset($code) && execAvailable() && is_executable("./test_code.sh")) {
$file = tempnam(sys_get_temp_dir(), "automark_");
$handle = fopen($file, "w");
fwrite($handle, $code);
fclose($handle);
$output = Array();
$status = 1;
exec("./test_code.sh " . $exercise . " " . $file, $output, $status);
unlink($file);
if ($status != 0) {
echo json_encode(Array("status" => "failure", "detail" => "Code does not pass tests"));
return;
}
}
$method="POST";
$content_type = "application/xml";
$sourcedid = htmlspecialchars($sourcedid);
$operation = 'replaceResultRequest';
$postBody = str_replace(
array('SOURCEDID', 'GRADE', 'OPERATION','MESSAGE'),
array($sourcedid, $grade, $operation, uniqid()),
getPOXGradeRequest());
$response = sendOAuthBodyPOST($method, $endpoint, $oauth_consumer_key, $oauth_consumer_secret, $content_type, $postBody);
try {
$retVal = parseResponse($response);
} catch(Exception $e) {
echo json_encode(Array("status" => "failure", "detail" => $e->getMessage(), "response" => $response));
return;
}
if ($retVal["imsx_codeMajor"] == "success") {
$retVal["status"] = "success";
} else {
$retVal["status"] = "failure";
$retVal["detail"] = "Unknown error occurred";
}
echo json_encode($retVal);
?>
<?php
session_start();
require_once("bower_components/pythonauto/util/lti_util.php");
?>
<!DOCTYPE html>
<html>
<head>
<title>Python Automarker</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="css/automarker.css" rel="stylesheet" media="screen">
<!--[if lt IE 9]>
<script src="bower_components/html5shiv/dist/html5shiv-printshiv.min.js"></script>
<![endif]-->
<script type="text/javascript" src="bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bower_components/Brython3.2.7-20160621-184325.tar/brython.js"></script>
<script type="text/javascript" src="bower_components/ace/src-min-noconflict/ace.js"></script>
<script type="text/javascript">
exercise_id = <?php echo isset($_REQUEST["exercise_id"]) ? $_REQUEST["exercise_id"] : 0; ?>;
jQuery(document).ready(function() {
editor = ace.edit("editor");
editor.session.setMode("ace/mode/python");
brython();
});
</script>
</head>
<body class="container">
<div class="row" id="header-row">
<div class="col-md-12 panel-primary">