initial commit blyad
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
__pycache__
|
||||
instance
|
||||
.venv
|
||||
*.sqlite3
|
||||
1
__init__.py
Normal file
1
__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# This file marks the directory as a Python package.
|
||||
89
app.py
Normal file
89
app.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for
|
||||
from models import db, Problem, Solution
|
||||
from utils import run_code_against_tests
|
||||
from leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard
|
||||
|
||||
|
||||
import os
|
||||
from problem_loader import load_problems_from_json, schedule_problem_reload
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3'
|
||||
db.init_app(app)
|
||||
|
||||
|
||||
|
||||
@app.before_request
|
||||
def setup():
|
||||
db.create_all()
|
||||
create_leaderboard_table() # Ensure leaderboard table exists
|
||||
# Load problems from JSON at startup
|
||||
json_path = os.path.join(os.path.dirname(__file__), 'problems.json')
|
||||
load_problems_from_json(json_path)
|
||||
# Schedule reload every 10 hours
|
||||
schedule_problem_reload(app, json_path, interval_hours=10)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
problems = Problem.query.all()
|
||||
leaderboard = get_leaderboard()
|
||||
# Map problem_id to title for leaderboard display
|
||||
problem_titles = {p.id: p.title for p in problems}
|
||||
return render_template('index.html', problems=problems, leaderboard=leaderboard, problem_titles=problem_titles)
|
||||
|
||||
@app.route('/problem/new', methods=['GET', 'POST'])
|
||||
def new_problem():
|
||||
if request.method == 'POST':
|
||||
title = request.form['title']
|
||||
description = request.form['description']
|
||||
test_code = request.form['test_code']
|
||||
problem = Problem(title=title, description=description, test_code=test_code)
|
||||
db.session.add(problem)
|
||||
db.session.commit()
|
||||
return redirect(url_for('index'))
|
||||
return render_template('new_problem.html')
|
||||
|
||||
@app.route('/problem/<int:problem_id>', methods=['GET', 'POST'])
|
||||
def view_problem(problem_id):
|
||||
problem = Problem.query.get_or_404(problem_id)
|
||||
result = None
|
||||
if request.method == 'POST':
|
||||
user_code = request.form['user_code']
|
||||
username = request.form.get('username', '').strip() or 'Anonymous'
|
||||
import tracemalloc
|
||||
tracemalloc.start()
|
||||
run_result = run_code_against_tests(user_code, problem.test_code)
|
||||
current, peak = tracemalloc.get_traced_memory()
|
||||
tracemalloc.stop()
|
||||
memory_used = peak // 1024 # in KB
|
||||
# Try to get the last line number executed (even for successful runs)
|
||||
line_number = None
|
||||
import ast
|
||||
try:
|
||||
tree = ast.parse(user_code)
|
||||
# Get the last line number in the user's code
|
||||
if hasattr(tree, 'body') and tree.body:
|
||||
last_node = tree.body[-1]
|
||||
line_number = getattr(last_node, 'lineno', None)
|
||||
except Exception:
|
||||
pass
|
||||
# If there was an error, try to get the error line number from the traceback
|
||||
if run_result['error']:
|
||||
tb = run_result['error']
|
||||
import traceback
|
||||
try:
|
||||
tb_lines = traceback.extract_tb(traceback.TracebackException.from_string(tb).stack)
|
||||
if tb_lines:
|
||||
line_number = tb_lines[-1].lineno
|
||||
except Exception:
|
||||
pass
|
||||
log_leaderboard(username, problem.id, run_result['runtime'], memory_used, line_number)
|
||||
solution = Solution(problem_id=problem.id, user_code=user_code, passed=run_result['passed'], output=run_result['output'])
|
||||
db.session.add(solution)
|
||||
db.session.commit()
|
||||
result = run_result
|
||||
|
||||
return render_template('problem.html', problem=problem, result=result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
46
leaderboard.py
Normal file
46
leaderboard.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask import g
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
def get_db():
|
||||
db = getattr(g, '_database', None)
|
||||
if db is None:
|
||||
db = g._database = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'db.sqlite3'))
|
||||
return db
|
||||
|
||||
def create_leaderboard_table():
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS leaderboard (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT,
|
||||
problem_id INTEGER,
|
||||
runtime REAL,
|
||||
memory_used INTEGER,
|
||||
line_number INTEGER,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
db.commit()
|
||||
|
||||
def log_leaderboard(username, problem_id, runtime, memory_used, line_number):
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO leaderboard (username, problem_id, runtime, memory_used, line_number)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (username, problem_id, runtime, memory_used, line_number))
|
||||
db.commit()
|
||||
|
||||
def get_leaderboard():
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute('''
|
||||
SELECT username, problem_id, runtime, memory_used, line_number, timestamp
|
||||
FROM leaderboard
|
||||
ORDER BY runtime ASC, memory_used ASC
|
||||
LIMIT 20
|
||||
''')
|
||||
return cursor.fetchall()
|
||||
17
models.py
Normal file
17
models.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class Problem(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(100), nullable=False)
|
||||
description = db.Column(db.Text, nullable=False)
|
||||
test_code = db.Column(db.Text, nullable=False) # Python code to test solution
|
||||
|
||||
class Solution(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
problem_id = db.Column(db.Integer, db.ForeignKey('problem.id'), nullable=False)
|
||||
user_code = db.Column(db.Text, nullable=False)
|
||||
passed = db.Column(db.Boolean, default=False)
|
||||
output = db.Column(db.Text)
|
||||
problem = db.relationship('Problem', backref=db.backref('solutions', lazy=True))
|
||||
37
problem_loader.py
Normal file
37
problem_loader.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from models import db, Problem
|
||||
from flask import current_app
|
||||
|
||||
def load_problems_from_json(json_path):
|
||||
if not os.path.exists(json_path):
|
||||
print(f"Problem JSON file not found: {json_path}")
|
||||
return
|
||||
with open(json_path, 'r') as f:
|
||||
problems = json.load(f)
|
||||
for p in problems:
|
||||
# Check if problem already exists by title
|
||||
existing = Problem.query.filter_by(title=p['title']).first()
|
||||
# Load test code from solution file if provided
|
||||
test_code = ''
|
||||
if 'solution' in p and os.path.exists(p['solution']):
|
||||
with open(p['solution'], 'r') as sf:
|
||||
test_code = sf.read()
|
||||
if existing:
|
||||
existing.description = p['description']
|
||||
existing.test_code = test_code
|
||||
else:
|
||||
new_problem = Problem(title=p['title'], description=p['description'], test_code=test_code)
|
||||
db.session.add(new_problem)
|
||||
db.session.commit()
|
||||
|
||||
def schedule_problem_reload(app, json_path, interval_hours=10):
|
||||
def reload_loop():
|
||||
while True:
|
||||
with app.app_context():
|
||||
load_problems_from_json(json_path)
|
||||
time.sleep(interval_hours * 3600)
|
||||
t = threading.Thread(target=reload_loop, daemon=True)
|
||||
t.start()
|
||||
12
problems.json
Normal file
12
problems.json
Normal file
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"title": "Reversed String",
|
||||
"description": "Reverse a String with a Function (revstring); the function is supposed to take the string as an argument and is supposed to return the reversed string and print it.",
|
||||
"solution": "problems/solution_reversed_string.py"
|
||||
},
|
||||
{
|
||||
"title": "Sort List",
|
||||
"description": "Sort a List with a Function (sortlist); the function is supposed to take the list as an argument and is supposed to return the sorted list and print it.",
|
||||
"solution": "problems/sortlist.py"
|
||||
}
|
||||
]
|
||||
14
problems/solution_reversed_string.py
Normal file
14
problems/solution_reversed_string.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import unittest
|
||||
|
||||
#<!-- The Function the User needs to write -->
|
||||
def revstring(x):
|
||||
return x[::-1]
|
||||
|
||||
#<!-- This Test, test if the function works -->
|
||||
class TestSolution(unittest.TestCase):
|
||||
def test_simple(self):
|
||||
x="";
|
||||
self.assertEqual(revstring(x), x[::-1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
11
problems/sortlist.py
Normal file
11
problems/sortlist.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import unittest
|
||||
|
||||
class TestSolution(unittest.TestCase):
|
||||
def test_sort(self):
|
||||
# define x as a empty array.
|
||||
# this will be used for the functiun ; a empty var does not work.
|
||||
self.x = []
|
||||
self.assertEqual(sortlist(self.x), sorted(self.x)) ## sort
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
25
readme.md
Normal file
25
readme.md
Normal file
@@ -0,0 +1,25 @@
|
||||
## under construction
|
||||
|
||||
### Like LeetCode
|
||||
|
||||
but more lightweight
|
||||
|
||||
if you want to contribute write tests like this:
|
||||
|
||||
```python
|
||||
import unittest
|
||||
|
||||
#<!-- The Function the User needs to write -->
|
||||
def revstring(x):
|
||||
return x[::-1]
|
||||
|
||||
#<!-- This Test, test if the function works -->
|
||||
class TestSolution(unittest.TestCase):
|
||||
def test_simple(self):
|
||||
# !! This needs to be dynamic ; if the user enters some shit then it is supposed to work too
|
||||
x="";
|
||||
self.assertEqual(revstring(x), x[::-1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
```
|
||||
260
static/style.css
Normal file
260
static/style.css
Normal file
@@ -0,0 +1,260 @@
|
||||
/* Reset and base styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Main heading */
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: -10px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 3px solid #3498db;
|
||||
font-size: 2.2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #34495e;
|
||||
margin: 30px 0 20px 0;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #34495e;
|
||||
margin: 25px 0 15px 0;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
/* Links and buttons */
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #e3f2fd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Primary action link (Submit New Problem) */
|
||||
a[href="/problem/new"] {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
a[href="/problem/new"]:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
/* Problem list */
|
||||
ul {
|
||||
list-style: none;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 25px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
li a {
|
||||
display: block;
|
||||
padding: 12px 20px;
|
||||
margin: -12px -20px;
|
||||
border-radius: 6px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
li a:hover {
|
||||
background-color: #f8f9fa;
|
||||
transform: translateX(5px);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* Problem page specific styles */
|
||||
.problem-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: #7f8c8d;
|
||||
}
|
||||
|
||||
.problem-desc {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
margin-bottom: 30px;
|
||||
font-size: 1.1em;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Editor section */
|
||||
.editor-section {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
#editor {
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
height: 400px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
form button[type="submit"] {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
form button[type="submit"]:hover {
|
||||
background-color: #229954;
|
||||
}
|
||||
|
||||
/* Results section */
|
||||
b {
|
||||
color: #2c3e50;
|
||||
display: inline-block;
|
||||
margin: 10px 0 5px 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f4f4f4;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #3498db;
|
||||
margin: 10px 0 20px 0;
|
||||
overflow-x: auto;
|
||||
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
pre[style*="color:red"] {
|
||||
border-left-color: #e74c3c;
|
||||
background-color: #fdf2f2;
|
||||
}
|
||||
|
||||
/* Status messages */
|
||||
p[style*="color:green"] {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
padding: 15px 20px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #27ae60;
|
||||
margin: 20px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p[style*="color:red"] {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
padding: 15px 20px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #e74c3c;
|
||||
margin: 20px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Back to Problems link */
|
||||
a[href="/"] {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a[href="/"]:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.problem-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.problem-desc, .editor-section, ul {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#editor {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
99
templates/index.html
Normal file
99
templates/index.html
Normal file
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Quick Problem Platform</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<style>
|
||||
.leaderboard-table {
|
||||
width: auto;
|
||||
min-width: 400px;
|
||||
max-width: 600px;
|
||||
border-collapse: collapse;
|
||||
margin: 2em 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px #0001;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaderboard-table th, .leaderboard-table td {
|
||||
padding: 0.7em 1em;
|
||||
text-align: center;
|
||||
}
|
||||
.leaderboard-table th {
|
||||
background: #f5f5f5;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.leaderboard-table tr:nth-child(even) {
|
||||
background: #fafbfc;
|
||||
}
|
||||
.leaderboard-table tr:nth-child(odd) {
|
||||
background: #f0f2f5;
|
||||
}
|
||||
.leaderboard-table td {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
.leaderboard-table tr:hover {
|
||||
background: #e0e7ff;
|
||||
}
|
||||
.problems-list {
|
||||
max-width: 600px;
|
||||
min-width: 400px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.problems-list ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.problems-list li {
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Quick Problem Platform</h1>
|
||||
<!--<a href="/problem/new">Submit New Problem</a>-->
|
||||
<section class="problems-list">
|
||||
<h2>Problems</h2>
|
||||
<ul>
|
||||
{% for problem in problems %}
|
||||
<li><a href="/problem/{{ problem.id }}">{{ problem.title }}</a></li>
|
||||
{% else %}
|
||||
<li>No problems yet.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Leaderboard</h2>
|
||||
<table class="leaderboard-table">
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>User</th>
|
||||
<th>Problem</th>
|
||||
<th>Runtime (s)</th>
|
||||
<th>Memory (KB)</th>
|
||||
<th>Line Number</th>
|
||||
<th>Timestamp</th>
|
||||
</tr>
|
||||
{% for entry in leaderboard %}
|
||||
<tr>
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ entry[0] }}</td>
|
||||
<td>{{ problem_titles.get(entry[1], 'Unknown') }}</td>
|
||||
<td>{{ '%.4f'|format(entry[2]) }}</td>
|
||||
<td>{{ entry[3] }}</td>
|
||||
<td>{{ entry[4] if entry[4] else '-' }}</td>
|
||||
<td>{{ entry[5] }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7">No leaderboard entries yet.</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
72
templates/problem.html
Normal file
72
templates/problem.html
Normal file
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ problem.title }}</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="problem-header">
|
||||
<button class="back-btn" onclick="window.location.href='/'">← Back</button>
|
||||
<h1 style="margin-bottom:0;">{{ problem.title }}</h1>
|
||||
</div>
|
||||
<div class="problem-desc">{{ problem.description }}</div>
|
||||
<div class="editor-section" style="max-width:1160;margin:0">
|
||||
<h2 style="margin-top:0;">Submit Your Solution (Python)</h2>
|
||||
<form method="post">
|
||||
<label for="username">Username (optional):</label>
|
||||
<input type="text" name="username" id="username" placeholder="Anonymous" style="margin-bottom:10px;">
|
||||
<div id="editor"></div>
|
||||
<textarea name="user_code" id="user_code" style="display:none;"></textarea>
|
||||
<div class="editor-actions">
|
||||
<button type="submit">Run & Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
||||
<script>
|
||||
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' } });
|
||||
require(['vs/editor/editor.main'], function() {
|
||||
var editor = monaco.editor.create(document.getElementById('editor'), {
|
||||
value: '',
|
||||
language: 'python',
|
||||
theme: 'vs-light',
|
||||
fontFamily: 'JetBrains Mono, monospace',
|
||||
fontLigatures: true,
|
||||
automaticLayout: true,
|
||||
fontSize: 16,
|
||||
minimap: { enabled: false }
|
||||
});
|
||||
document.querySelector('form').addEventListener('submit', function(e) {
|
||||
var code = editor.getValue();
|
||||
if (!code.trim()) {
|
||||
alert('Please enter your code before submitting.');
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
document.getElementById('user_code').value = code;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% if result %}
|
||||
<div class="editor-section" style="max-width:1160;margin:0;margin-top: 5px;">
|
||||
<h3>Result:</h3>
|
||||
<b>Runtime:</b> {{ '%.4f'|format(result.runtime) }} seconds<br>
|
||||
<b>Output:</b>
|
||||
<pre>{{ result.output }}</pre>
|
||||
{% if result.error %}
|
||||
<b>Error:</b>
|
||||
<pre style="color:red;">{{ result.error }}</pre>
|
||||
{% endif %}
|
||||
{% if result.passed %}
|
||||
<p style="color:green;">Passed!</p>
|
||||
{% else %}
|
||||
<p style="color:red;">Failed!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!--<a href="/">Back to Problems</a>-->
|
||||
</body>
|
||||
</html>
|
||||
51
utils.py
Normal file
51
utils.py
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
import io
|
||||
|
||||
def run_code_against_tests(user_code, test_code):
|
||||
import tempfile
|
||||
import subprocess
|
||||
local_ns = {}
|
||||
output = ''
|
||||
start = time.perf_counter()
|
||||
error = None
|
||||
passed = False
|
||||
try:
|
||||
# Check if unittest is used in test_code
|
||||
if 'unittest' in test_code:
|
||||
# Write user code + test code to a temp file
|
||||
with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False) as f:
|
||||
f.write(user_code + '\n' + test_code)
|
||||
f.flush()
|
||||
f_name = f.name
|
||||
# Run the file as a subprocess
|
||||
proc = subprocess.run([sys.executable, f_name], capture_output=True, text=True, timeout=10)
|
||||
output = proc.stdout + proc.stderr
|
||||
passed = proc.returncode == 0
|
||||
error = None if passed else output
|
||||
else:
|
||||
# Capture stdout
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = mystdout = io.StringIO()
|
||||
# Execute user code
|
||||
exec(user_code, {}, local_ns)
|
||||
# Execute test code (should raise AssertionError if fail)
|
||||
exec(test_code, local_ns, local_ns)
|
||||
passed = True
|
||||
except Exception as e:
|
||||
passed = False
|
||||
error = traceback.format_exc()
|
||||
finally:
|
||||
if 'mystdout' in locals():
|
||||
output = mystdout.getvalue() or output
|
||||
sys.stdout = old_stdout if 'old_stdout' in locals() else sys.stdout
|
||||
runtime = time.perf_counter() - start
|
||||
result = {
|
||||
'passed': passed,
|
||||
'output': output,
|
||||
'runtime': runtime,
|
||||
'error': error if not passed else None
|
||||
}
|
||||
return result
|
||||
Reference in New Issue
Block a user