28 Commits

Author SHA1 Message Date
4ae5f12633 Your fixed commit message here 2025-08-22 20:06:27 +02:00
1ef6bdfd80 creater 2025-08-20 19:48:45 +02:00
f28a6b36ef epileptic turtles 2025-08-18 21:59:03 +02:00
6c8f34acb9 fixed some shit ; added pagination 2025-08-18 21:52:54 +02:00
27f955151a hardnened 2025-08-18 21:16:46 +02:00
f3baec17e4 new problem and i added better support for sorting and a overall better script 2025-08-17 18:58:12 +02:00
996cfd2821 rewrote 2025-08-17 16:04:13 +02:00
2577c96258 Update .gitattributes 2025-08-17 13:45:16 +00:00
9532213adf update some bs
+ readme
+ some change in the bash script using ports
2025-08-17 15:43:11 +02:00
734ec1dc73 Update readme.md 2025-08-17 12:25:00 +00:00
e8e1b82d6b ignore stuff 2025-08-17 12:24:48 +02:00
8dd5fcbeb7 md for database as it was fucked up in windows script 2025-08-17 12:19:18 +02:00
b6ab591054 Merge pull request 'darkmode' (#2) from darkmode into main
Reviewed-on: #2
2025-08-17 09:50:57 +00:00
5dc45b9a9b this works now, ironed out shit and made stuff work 2025-08-17 11:47:56 +02:00
57a7b0e68f semi working darkmode 2025-08-16 21:44:29 +02:00
68b7b81741 new problem and deleted the obsolete problem loader 2025-08-16 20:44:47 +02:00
e97dde65fb animation ; fix the displaying and calculating tmrw 2025-08-14 22:08:47 +02:00
6079813e2c stashcom 2025-08-14 22:05:20 +02:00
04dc638cf0 if the user submitted solution is shit wrong then fucking dont add to the shit database.
also removed unnccessary fucking function
2025-08-13 22:56:06 +02:00
38c3256f19 zajebis2 2025-08-12 20:42:52 +02:00
1374cb9cb1 zajebis 2025-08-12 20:42:26 +02:00
c1ef310f6a shit didnt woirk. frucky ou 2025-08-12 20:37:59 +02:00
525297f19b delete templates which was empty 2025-08-12 20:17:14 +02:00
89ea87951e made this a hell of a lot better 2025-08-12 20:16:46 +02:00
c7c1b8ecd6 some more info and some difficulty added! 2025-08-12 18:56:52 +02:00
0bffdf612c refactored the prb page and added fib seqq 2025-08-12 16:37:35 +02:00
a03f9ddb14 Update readme.md 2025-08-12 11:52:15 +00:00
3f1f709f30 Merge pull request 'shitdontwork' (#1) from shitdontwork into main
Reviewed-on: #1
2025-08-12 11:47:10 +00:00
54 changed files with 5160 additions and 1414 deletions

12
.gitattributes vendored Normal file
View File

@@ -0,0 +1,12 @@
# Detect Markdown files
*.md linguist-language=Markdown
# Ignore CSS files for language detection
*.css linguist-vendored
# Some LF stuff so that linux works ; windows can fuck off
* text=auto eol=lf
*.png binary
*.jpg binary
*.gif binary
*.zip binary

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
__pycache__ __pycache__
instance instance
.venv .venv
*.sqlite3 venv
*.sqlite3

View File

@@ -1,37 +0,0 @@
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()

View File

@@ -1 +0,0 @@
this is a easy sorting problem **it is solvable in less than 2 seconds**

View File

@@ -1,17 +0,0 @@
import unittest
# This is the function the user is expected to write.
# Its a really simple one, the user can choose not to type tho.
#def sortlist(lst = [4,3,2,1]) -> list:
#return sorted(lst)
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)) # pyright: ignore[reportUndefinedVariable]
if __name__ == "__main__":
unittest.main()

902
qtc.py Normal file
View File

@@ -0,0 +1,902 @@
import sys
import json
import os
import logging
from pathlib import Path
from typing import Optional, Dict, Any, List, Tuple
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget,
QLabel, QLineEdit, QComboBox, QTextEdit, QPushButton, QStatusBar,
QMessageBox, QDialog, QListWidget, QSplitter, QFrame, QSizePolicy,
QScrollArea, QFileDialog, QToolTip
)
from PyQt6.QtCore import Qt, QSize, pyqtSignal, QTimer
from PyQt6.QtGui import QFont, QTextOption, QSyntaxHighlighter, QTextCharFormat, QColor, QTextCursor, QKeyEvent, QTextDocument
from PyQt6.QtWebEngineWidgets import QWebEngineView
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger("ProblemCreator")
# Optional imports with proper error handling
OPTIONAL_DEPENDENCIES = {}
try:
import markdown
OPTIONAL_DEPENDENCIES['markdown'] = True
except ImportError:
logger.warning("Markdown library not available, using basic conversion")
OPTIONAL_DEPENDENCIES['markdown'] = False
try:
from pygments import lex
from pygments.lexers import PythonLexer
from pygments.styles import get_style_by_name
OPTIONAL_DEPENDENCIES['pygments'] = True
except ImportError:
logger.warning("Pygments not available, syntax highlighting will be disabled")
OPTIONAL_DEPENDENCIES['pygments'] = False
class CodeEditor(QTextEdit):
"""A code editor with syntax highlighting and advanced auto-indentation."""
def __init__(self, parent=None):
super().__init__(parent)
self.setFont(QFont("Consolas", 10))
self.indent_width = 4
self.setTabStopDistance(QFont("Consolas", 10).pointSize() * self.indent_width)
# Setup syntax highlighting if available
if OPTIONAL_DEPENDENCIES.get('pygments', False):
self.highlighter = PythonHighlighter(self.document())
# Tips for test case editing
self.tips = [
"💡 Tip: Use descriptive test method names like test_empty_input()",
"💡 Tip: Test edge cases like empty inputs, large inputs, and invalid inputs",
"💡 Tip: Use assertEqual for exact matches, assertTrue/False for boolean checks",
"💡 Tip: Test both valid and invalid inputs to ensure robustness",
"💡 Tip: Consider using setUp() method for common test setup",
"💡 Tip: Use parameterized tests if you have many similar test cases",
"💡 Tip: Make sure your tests are independent of each other",
"💡 Tip: Test not only for correct outputs but also for proper error handling"
]
self.current_tip_index = 0
self.tip_timer = QTimer(self)
self.tip_timer.timeout.connect(self.show_next_tip)
self.tip_timer.start(10000) # Show a new tip every 10 seconds
def show_next_tip(self):
"""Show the next tip in the status bar."""
if self.parent() and hasattr(self.parent().parent().parent().parent(), 'statusBar'):
status_bar = self.parent().parent().parent().parent().statusBar()
if status_bar:
tip = self.tips[self.current_tip_index]
status_bar.showMessage(tip)
self.current_tip_index = (self.current_tip_index + 1) % len(self.tips)
def keyPressEvent(self, event: QKeyEvent):
"""Handle key press events for auto-indentation and pairing."""
key = event.key()
modifiers = event.modifiers()
cursor = self.textCursor()
# Tab key
if key == Qt.Key.Key_Tab:
if cursor.hasSelection():
self.indentSelection()
else:
# Insert spaces
cursor.insertText(" " * self.indent_width)
return
# Shift+Tab key
elif key == Qt.Key.Key_Backtab:
if cursor.hasSelection():
self.dedentSelection()
else:
self.dedentLine()
return
# Return key
elif key == Qt.Key.Key_Return:
# Get current line
cursor.movePosition(QTextCursor.MoveOperation.StartOfLine)
cursor.movePosition(QTextCursor.MoveOperation.EndOfLine, QTextCursor.MoveMode.KeepAnchor)
line_text = cursor.selectedText()
# Calculate indentation
indent = len(line_text) - len(line_text.lstrip())
# Check if line ends with colon
ends_with_colon = line_text.rstrip().endswith(':')
# Insert newline with indentation
cursor = self.textCursor()
cursor.insertText("\n" + " " * indent)
# Add extra indentation if line ended with colon
if ends_with_colon:
cursor.insertText(" " * self.indent_width)
return
# Auto-pairing
elif key == Qt.Key.Key_ParenLeft:
cursor.insertText("()")
cursor.movePosition(QTextCursor.MoveOperation.Left)
self.setTextCursor(cursor)
return
elif key == Qt.Key.Key_BracketLeft:
cursor.insertText("[]")
cursor.movePosition(QTextCursor.MoveOperation.Left)
self.setTextCursor(cursor)
return
elif key == Qt.Key.Key_BraceLeft:
cursor.insertText("{}")
cursor.movePosition(QTextCursor.MoveOperation.Left)
self.setTextCursor(cursor)
return
elif key == Qt.Key.Key_QuoteDbl:
cursor.insertText('""')
cursor.movePosition(QTextCursor.MoveOperation.Left)
self.setTextCursor(cursor)
return
elif key == Qt.Key.Key_Apostrophe:
cursor.insertText("''")
cursor.movePosition(QTextCursor.MoveOperation.Left)
self.setTextCursor(cursor)
return
elif key == Qt.Key.Key_Colon and modifiers == Qt.KeyboardModifier.NoModifier:
# Check if we're at the end of the line
cursor.movePosition(QTextCursor.MoveOperation.EndOfLine)
if self.textCursor().position() == cursor.position():
cursor.insertText(":")
return
# Default behavior
super().keyPressEvent(event)
def indentSelection(self):
"""Indent all selected lines."""
cursor = self.textCursor()
start = cursor.selectionStart()
end = cursor.selectionEnd()
# Move to start of selection
cursor.setPosition(start)
cursor.movePosition(QTextCursor.MoveOperation.StartOfLine)
# Indent each line in selection
while cursor.position() <= end:
cursor.insertText(" " * self.indent_width)
end += self.indent_width
if not cursor.movePosition(QTextCursor.MoveOperation.Down):
break
# Restore selection
cursor.setPosition(start)
cursor.setPosition(end, QTextCursor.MoveMode.KeepAnchor)
self.setTextCursor(cursor)
def dedentSelection(self):
"""Dedent all selected lines."""
cursor = self.textCursor()
start = cursor.selectionStart()
end = cursor.selectionEnd()
# Move to start of selection
cursor.setPosition(start)
cursor.movePosition(QTextCursor.MoveOperation.StartOfLine)
# Dedent each line in selection
while cursor.position() <= end:
# Check for spaces at beginning of line
line_start = cursor.position()
cursor.movePosition(QTextCursor.MoveOperation.EndOfLine, QTextCursor.MoveMode.KeepAnchor)
line_text = cursor.selectedText()
# Count leading spaces
leading_spaces = min(len(line_text) - len(line_text.lstrip()), self.indent_width)
if leading_spaces > 0:
# Remove leading spaces
cursor.setPosition(line_start)
cursor.movePosition(QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, leading_spaces)
cursor.removeSelectedText()
end -= leading_spaces
if not cursor.movePosition(QTextCursor.MoveOperation.Down):
break
# Restore selection
cursor.setPosition(max(0, start - self.indent_width))
cursor.setPosition(max(0, end - self.indent_width), QTextCursor.MoveMode.KeepAnchor)
self.setTextCursor(cursor)
def dedentLine(self):
"""Dedent the current line."""
cursor = self.textCursor()
cursor.movePosition(QTextCursor.MoveOperation.StartOfLine)
# Check for spaces at beginning of line
line_start = cursor.position()
cursor.movePosition(QTextCursor.MoveOperation.EndOfLine, QTextCursor.MoveMode.KeepAnchor)
line_text = cursor.selectedText()
# Count leading spaces
leading_spaces = min(len(line_text) - len(line_text.lstrip()), self.indent_width)
if leading_spaces > 0:
# Remove leading spaces
cursor.setPosition(line_start)
cursor.movePosition(QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, leading_spaces)
cursor.removeSelectedText()
class PythonHighlighter(QSyntaxHighlighter):
"""Syntax highlighter for Python code using Pygments."""
def __init__(self, document):
super().__init__(document)
self._setup_formats()
def _setup_formats(self):
"""Setup text formats for different token types."""
self.formats = {}
# Define syntax highlighting formats
keyword_format = QTextCharFormat()
keyword_format.setForeground(QColor("#0000FF"))
keyword_format.setFontWeight(QFont.Weight.Bold)
self.formats['keyword'] = keyword_format
string_format = QTextCharFormat()
string_format.setForeground(QColor("#008000"))
self.formats['string'] = string_format
comment_format = QTextCharFormat()
comment_format.setForeground(QColor("#808080"))
comment_format.setFontItalic(True)
self.formats['comment'] = comment_format
function_format = QTextCharFormat()
function_format.setForeground(QColor("#000080"))
function_format.setFontWeight(QFont.Weight.Bold)
self.formats['function'] = function_format
number_format = QTextCharFormat()
number_format.setForeground(QColor("#FF8C00"))
self.formats['number'] = number_format
# Python keywords
self.keywords = [
'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del',
'elif', 'else', 'except', 'False', 'finally', 'for', 'from', 'global',
'if', 'import', 'in', 'is', 'lambda', 'None', 'nonlocal', 'not', 'or',
'pass', 'raise', 'return', 'True', 'try', 'while', 'with', 'yield'
]
# unittest keywords
self.unittest_keywords = [
'TestCase', 'setUp', 'tearDown', 'setUpClass', 'tearDownClass',
'assertEqual', 'assertTrue', 'assertFalse', 'assertRaises',
'assertAlmostEqual', 'assertNotEqual', 'assertIn', 'assertNotIn',
'assertIs', 'assertIsNot', 'assertIsNone', 'assertIsNotNone',
'assertIsInstance', 'assertNotIsInstance', 'assertDictEqual',
'assertListEqual', 'assertTupleEqual', 'assertSetEqual',
'assertSequenceEqual', 'assertMultiLineEqual', 'assertGreater',
'assertGreaterEqual', 'assertLess', 'assertLessEqual', 'assertRegex',
'assertNotRegex', 'assertCountEqual'
]
def highlightBlock(self, text):
"""Apply syntax highlighting to the current text block."""
# Check if we should use pygments
if OPTIONAL_DEPENDENCIES.get('pygments', False):
self._highlight_with_pygments(text)
else:
self._highlight_with_basic_rules(text)
def _highlight_with_pygments(self, text):
"""Use pygments for syntax highlighting if available."""
try:
# Get the text from the current block
block = self.currentBlock()
start_pos = block.position()
end_pos = start_pos + len(text)
full_text = self.document().toPlainText()
# Lex the code and apply formats
for token, value in lex(full_text, PythonLexer()):
token_str = str(token)
token_start = full_text.find(value, start_pos)
# Skip if token is not in current block
if token_start < start_pos or token_start >= end_pos:
continue
# Calculate length within current block
token_len = min(len(value), end_pos - token_start)
# Apply appropriate format
if 'Keyword' in token_str:
self.setFormat(token_start - start_pos, token_len, self.formats['keyword'])
elif 'String' in token_str:
self.setFormat(token_start - start_pos, token_len, self.formats['string'])
elif 'Comment' in token_str:
self.setFormat(token_start - start_pos, token_len, self.formats['comment'])
elif 'Name' in token_str and 'Function' in token_str:
self.setFormat(token_start - start_pos, token_len, self.formats['function'])
elif 'Number' in token_str:
self.setFormat(token_start - start_pos, token_len, self.formats['number'])
except Exception as e:
logger.error(f"Error during pygments highlighting: {e}")
# Fall back to basic highlighting
self._highlight_with_basic_rules(text)
def _highlight_with_basic_rules(self, text):
"""Use basic rules for syntax highlighting."""
# Highlight keywords
for keyword in self.keywords + self.unittest_keywords:
pattern = r'\b' + keyword + r'\b'
index = 0
while index < len(text):
index = text.find(keyword, index)
if index == -1:
break
# Check if it's really a word (not part of another word)
if (index == 0 or not text[index-1].isalnum()) and \
(index + len(keyword) >= len(text) or not text[index + len(keyword)].isalnum()):
if keyword in self.keywords:
self.setFormat(index, len(keyword), self.formats['keyword'])
else:
self.setFormat(index, len(keyword), self.formats['function'])
index += len(keyword)
# Highlight strings
import re
string_pattern = re.compile(r'(\".*?\")|(\'.*?\')')
for match in string_pattern.finditer(text):
start, end = match.span()
self.setFormat(start, end - start, self.formats['string'])
# Highlight comments
comment_pattern = re.compile(r'#.*')
for match in comment_pattern.finditer(text):
start, end = match.span()
self.setFormat(start, end - start, self.formats['comment'])
# Highlight numbers
number_pattern = re.compile(r'\b\d+\b')
for match in number_pattern.finditer(text):
start, end = match.span()
self.setFormat(start, end - start, self.formats['number'])
class MarkdownEditor(QWidget):
"""A markdown editor with live preview."""
def __init__(self, parent=None):
super().__init__(parent)
# Create split view
self.splitter = QSplitter(Qt.Orientation.Horizontal)
layout = QVBoxLayout(self)
layout.addWidget(self.splitter)
# Left side - text editor
self.editor = QTextEdit()
self.editor.setFont(QFont("Consolas", 10))
self.editor.textChanged.connect(self.update_preview)
self.splitter.addWidget(self.editor)
# Right side - preview
self.preview = QWebEngineView()
self.splitter.addWidget(self.preview)
# Set initial sizes
self.splitter.setSizes([400, 400])
def update_preview(self):
"""Update the markdown preview."""
# Get the markdown text
markdown_text = self.editor.toPlainText()
# Convert to HTML
html_content = self._markdown_to_html(markdown_text)
# Update the preview
self.preview.setHtml(html_content)
def _markdown_to_html(self, text):
"""Convert markdown text to HTML."""
if OPTIONAL_DEPENDENCIES.get('markdown', False):
# Use the markdown library if available
html = markdown.markdown(text)
else:
# Fallback to basic conversion
html = text
html = html.replace("# ", "<h1>").replace("\n# ", "</h1>\n<h1>") + "</h1>"
html = html.replace("## ", "<h2>").replace("\n## ", "</h2>\n<h2>") + "</h2>"
html = html.replace("### ", "<h3>").replace("\n### ", "</h3>\n<h3>") + "</h3>"
html = html.replace("**", "<strong>").replace("**", "</strong>")
html = html.replace("*", "<em>").replace("*", "</em>")
html = html.replace("`", "<code>").replace("`", "</code>")
html = html.replace("\n", "<br>")
# Wrap in proper HTML structure
return f"""
<html>
<head>
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 20px;
line-height: 1.6;
color: #333;
}}
code {{
background-color: #f6f8fa;
padding: 2px 6px;
border-radius: 4px;
font-family: 'Consolas', monospace;
}}
pre {{
background-color: #f6f8fa;
padding: 12px;
border-radius: 6px;
overflow: auto;
}}
pre code {{
background: none;
padding: 0;
}}
blockquote {{
border-left: 4px solid #ddd;
margin-left: 0;
padding-left: 16px;
color: #666;
}}
</style>
</head>
<body>
{html}
</body>
</html>
"""
def setPlainText(self, text):
"""Set the editor text."""
self.editor.setPlainText(text)
def toPlainText(self):
"""Get the editor text."""
return self.editor.toPlainText()
class LoadProblemDialog(QDialog):
"""Dialog for loading existing problems."""
def __init__(self, problems, parent=None):
super().__init__(parent)
self.setWindowTitle("Load Existing Problem")
self.setModal(True)
self.setMinimumSize(400, 300)
layout = QVBoxLayout(self)
label = QLabel("Select a problem to load:")
label.setFont(QFont("Arial", 10, QFont.Weight.Bold))
layout.addWidget(label)
self.list_widget = QListWidget()
self.list_widget.addItems(sorted(problems))
layout.addWidget(self.list_widget)
button_layout = QHBoxLayout()
self.load_button = QPushButton("Load")
self.load_button.clicked.connect(self.accept)
button_layout.addWidget(self.load_button)
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(self.reject)
button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout)
def selected_problem(self):
"""Get the selected problem name."""
items = self.list_widget.selectedItems()
return items[0].text() if items else None
class ProblemCreatorApp(QMainWindow):
"""Main application for creating coding problems."""
def __init__(self):
super().__init__()
self.setWindowTitle("Coding Problem Creator")
self.setGeometry(100, 100, 1200, 900)
# Set default paths
self.base_path = Path("src/problems")
# Initialize UI
self.create_widgets()
self.statusBar().showMessage("Ready to create a new problem...")
def create_widgets(self):
"""Create all UI widgets."""
# Central widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Main layout
main_layout = QVBoxLayout(central_widget)
# Create tab widget
self.tab_widget = QTabWidget()
main_layout.addWidget(self.tab_widget)
# Problem Info tab
self.info_tab = QWidget()
self.tab_widget.addTab(self.info_tab, "Problem Info")
self.create_info_tab()
# Markdown Description tab
self.markdown_tab = QWidget()
self.tab_widget.addTab(self.markdown_tab, "Markdown Description")
self.create_markdown_tab()
# Test Code tab
self.test_tab = QWidget()
self.tab_widget.addTab(self.test_tab, "Test Code")
self.create_test_tab()
# Buttons at the bottom
button_layout = QHBoxLayout()
self.create_button = QPushButton("Create Problem")
self.create_button.clicked.connect(self.create_problem)
button_layout.addWidget(self.create_button)
self.clear_button = QPushButton("Clear All")
self.clear_button.clicked.connect(self.clear_all)
button_layout.addWidget(self.clear_button)
self.load_button = QPushButton("Load Existing")
self.load_button.clicked.connect(self.load_existing)
button_layout.addWidget(self.load_button)
main_layout.addLayout(button_layout)
def create_info_tab(self):
"""Create the Problem Info tab."""
layout = QVBoxLayout(self.info_tab)
# Title
title_label = QLabel("Coding Problem Creator")
title_font = QFont("Arial", 16, QFont.Weight.Bold)
title_label.setFont(title_font)
layout.addWidget(title_label)
# Problem Name
name_layout = QHBoxLayout()
name_label = QLabel("Problem Name:")
name_label.setFont(QFont("Arial", 10, QFont.Weight.Bold))
name_layout.addWidget(name_label)
self.problem_name = QLineEdit()
self.problem_name.setFont(QFont("Arial", 10))
name_layout.addWidget(self.problem_name)
layout.addLayout(name_layout)
# Difficulty
difficulty_layout = QHBoxLayout()
difficulty_label = QLabel("Difficulty:")
difficulty_label.setFont(QFont("Arial", 10, QFont.Weight.Bold))
difficulty_layout.addWidget(difficulty_label)
self.difficulty = QComboBox()
self.difficulty.addItems(["easy", "medium", "hard"])
self.difficulty.setCurrentText("medium")
difficulty_layout.addWidget(self.difficulty)
difficulty_layout.addStretch()
layout.addLayout(difficulty_layout)
# Plain Text Description
desc_label = QLabel("Plain Text Description:")
desc_label.setFont(QFont("Arial", 10, QFont.Weight.Bold))
layout.addWidget(desc_label)
self.description_text = QTextEdit()
self.description_text.setFont(QFont("Arial", 10))
self.description_text.setAcceptRichText(False)
layout.addWidget(self.description_text)
def create_markdown_tab(self):
"""Create the Markdown Description tab."""
layout = QVBoxLayout(self.markdown_tab)
self.description_editor = MarkdownEditor()
layout.addWidget(self.description_editor)
def create_test_tab(self):
"""Create the Test Code tab."""
layout = QVBoxLayout(self.test_tab)
# Add tips label
tips_label = QLabel("💡 Tips for writing good test cases will appear in the status bar")
tips_label.setFont(QFont("Arial", 9))
tips_label.setStyleSheet("color: #666; padding: 5px;")
layout.addWidget(tips_label)
self.test_code_editor = CodeEditor()
layout.addWidget(self.test_code_editor)
# Insert template code
self._insert_template_code()
def _insert_template_code(self):
"""Insert template test code into the editor."""
template_code = '''import unittest
class TestSolution(unittest.TestCase):
def test_example_case(self):
"""
Test the provided example case.
"""
solution = Solution()
result = solution.solve("input")
self.assertEqual(result, "expected_output")
def test_edge_case_empty_input(self):
"""
Test with empty input.
"""
solution = Solution()
result = solution.solve("")
self.assertEqual(result, "")
def test_edge_case_large_input(self):
"""
Test with a large input to check performance.
"""
solution = Solution()
large_input = "a" * 1000
result = solution.solve(large_input)
self.assertTrue(result) # Adjust based on expected behavior
if __name__ == "__main__":
unittest.main()
'''
self.test_code_editor.setPlainText(template_code)
def validate_inputs(self):
"""Validate all form inputs."""
if not self.problem_name.text().strip():
QMessageBox.critical(self, "Error", "Problem name is required!")
return False
if not self.description_text.toPlainText().strip():
QMessageBox.critical(self, "Error", "Plain text description is required!")
return False
if not self.description_editor.toPlainText().strip():
QMessageBox.critical(self, "Error", "Markdown description is required!")
return False
test_code = self.test_code_editor.toPlainText().strip()
if not test_code or "pass" in test_code and len(test_code) < 100:
reply = QMessageBox.question(
self,
"Confirm",
"The test code seems minimal. Are you sure you want to proceed?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.No:
return False
# Validate problem name (should be filesystem-safe)
name = self.problem_name.text().strip()
if not name.replace("_", "").replace("-", "").replace(" ", "").isalnum():
QMessageBox.critical(
self,
"Error",
"Problem name should only contain letters, numbers, spaces, hyphens, and underscores!"
)
return False
return True
def create_problem(self):
"""Create a new problem from the form data."""
if not self.validate_inputs():
return
try:
# Get values
problem_name = self.problem_name.text().strip()
description_text = self.description_text.toPlainText().strip() # Plain text
description_md = self.description_editor.toPlainText().strip() # Markdown
difficulty = self.difficulty.currentText()
test_code = self.test_code_editor.toPlainText().strip()
# Create safe folder name (replace spaces with underscores)
folder_name = problem_name.replace(" ", "_").lower()
# Create directory structure
problem_path = self.base_path / folder_name
# Create directories if they don't exist
problem_path.mkdir(parents=True, exist_ok=True)
# Create manifest.json - Include both description fields
manifest = {
"title": problem_name,
"description": description_text, # Plain text description
"description_md": f"problems/{folder_name}/description.md", # Markdown file path
"test_code": f"problems/{folder_name}/test.py",
"difficulty": difficulty
}
manifest_path = problem_path / "manifest.json"
with open(manifest_path, 'w', encoding='utf-8') as f:
json.dump(manifest, f, indent=4, ensure_ascii=False)
# Create description.md
description_md_path = problem_path / "description.md"
with open(description_md_path, 'w', encoding='utf-8') as f:
f.write(description_md)
# Create test.py
test_py_path = problem_path / "test.py"
with open(test_py_path, 'w', encoding='utf-8') as f:
f.write(test_code)
self.statusBar().showMessage(f"✓ Problem '{problem_name}' created successfully in {problem_path}")
logger.info(f"Created problem: {problem_name} at {problem_path}")
reply = QMessageBox.question(
self,
"Success",
f"Problem '{problem_name}' created successfully!\n\n"
f"Location: {problem_path}\n\n"
"Would you like to open the folder?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self.open_folder(problem_path)
except Exception as e:
error_msg = f"Error creating problem: {str(e)}"
self.statusBar().showMessage(error_msg)
logger.error(error_msg)
QMessageBox.critical(self, "Error", error_msg)
def open_folder(self, path):
"""Cross-platform folder opening."""
try:
if sys.platform == "win32":
os.startfile(path)
elif sys.platform == "darwin": # macOS
os.system(f"open '{path}'")
else: # Linux and other Unix-like
os.system(f"xdg-open '{path}'")
except Exception as e:
error_msg = f"Could not open folder: {str(e)}"
logger.warning(error_msg)
QMessageBox.warning(self, "Warning", error_msg)
def clear_all(self):
"""Clear all form fields."""
reply = QMessageBox.question(
self,
"Confirm",
"Clear all fields?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self.problem_name.clear()
self.description_text.clear()
self.description_editor.setPlainText("")
self.difficulty.setCurrentText("medium")
self.test_code_editor.clear()
# Re-insert template
self._insert_template_code()
self.statusBar().showMessage("All fields cleared.")
logger.info("Cleared all form fields")
def load_existing(self):
"""Load an existing problem for editing."""
try:
if not self.base_path.exists():
QMessageBox.warning(self, "Warning", "No problems directory found!")
return
# Get list of existing problems
problems = [d.name for d in self.base_path.iterdir() if d.is_dir()]
if not problems:
QMessageBox.information(self, "Info", "No existing problems found!")
return
# Create and show selection dialog
dialog = LoadProblemDialog(problems, self)
if dialog.exec() == QDialog.DialogCode.Accepted:
selected_problem = dialog.selected_problem()
if selected_problem:
self.load_problem_data(selected_problem)
except Exception as e:
error_msg = f"Error loading existing problems: {str(e)}"
logger.error(error_msg)
QMessageBox.critical(self, "Error", error_msg)
def load_problem_data(self, problem_name):
"""Load problem data into the form."""
try:
problem_path = self.base_path / problem_name
manifest_path = problem_path / "manifest.json"
test_path = problem_path / "test.py"
desc_path = problem_path / "description.md"
# Load manifest
with open(manifest_path, 'r', encoding='utf-8') as f:
manifest = json.load(f)
# Load test code
test_code = ""
if test_path.exists():
with open(test_path, 'r', encoding='utf-8') as f:
test_code = f.read()
# Load markdown description
description_md = ""
if desc_path.exists():
with open(desc_path, 'r', encoding='utf-8') as f:
description_md = f.read()
# Load plain text description from manifest
description_text = manifest.get('description', '')
# Populate fields
self.problem_name.setText(manifest["title"])
self.description_text.setPlainText(description_text)
self.description_editor.setPlainText(description_md)
self.difficulty.setCurrentText(manifest["difficulty"])
self.test_code_editor.setPlainText(test_code)
self.statusBar().showMessage(f"Loaded problem: {problem_name}")
logger.info(f"Loaded problem: {problem_name}")
except Exception as e:
error_msg = f"Error loading problem data: {str(e)}"
logger.error(error_msg)
QMessageBox.critical(self, "Error", error_msg)
def main():
"""Main application entry point."""
app = QApplication(sys.argv)
# Set application style
app.setStyle('Fusion')
window = ProblemCreatorApp()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

179
readme.md
View File

@@ -1,25 +1,174 @@
## under construction # QPP - Quick Problem Platfrom
### Like LeetCode This is a lightweight, LeetCode-inspired problem-solving platform. You can run the server locally, contribute problems, and write unit tests.
but more lightweight ---
if you want to contribute write tests like this: ## Getting Started
Run the provided bash/_batch_ script to start the server.
---
## File Structure for Problems
Create a folder inside `/problems/` named after your problem. Each folder **must** contain:
* `manifest.json` # Dont worry you can change the info anytime to reload
* `test.py`
* `description.md`
**Example `manifest.json`:**
```json
{
"title": "Title of the Problem",
"description": "Write a very short description here",
"description_md": "problems/problempath/description.md",
"difficulty": "easy || medium || hard",
"test_code": "problems/problempath/test.py"
}
```
> This structure is mandatory but ensures the easiest workflow.
---
## Writing Problem Descriptions
* Use **simple and easy-to-understand language**. Avoid overly technical explanations.
* Syntax:
* Normal Markdown
* Start headings with `##` (looks cleaner than `#`)
* Include cross-links to resources, e.g., [W3Schools](https://www.w3schools.com/) or [Python Docs](https://docs.python.org/3/)
* Good formatting is always appreciated
---
## Developing & Running Locally
To run the backend during development:
```bash
python -m flask --app ./src/app.py --host=0.0.0.0 --port=5000
```
For production testing:
**Linux:**
```bash
python -m gunicorn -w 4 -b 0.0.0.0:8000 src.app:app
```
**Windows:**
```bat
:: Create database folder if missing
md .\src\database
python -m waitress --listen=0.0.0.0:8000 src.app:app
```
> Ensure all required packages are installed via `requirements.txt`. Python is versatile enough for this small backend.
---
### Migrating Legacy Code
When removing or refactoring legacy code:
1. Check if the code is used anywhere; if critical, branch into a testing branch first.
2. Ensure essential functionality is preserved.
---
## Committing Changes
**WE NEED FRONTED PEOPLE!!**, I have no Idea how that works, please someone ( if they are interested help )
* Ensure your editor uses **LF** line endings (`\n`) instead of CRLF.
* To automatically fix CRLF on commit:
```bash
git config core.autocrlf input
git add --renormalize .
git commit -m "Major Change"
```
* Recommended workflow:
1. Fork
2. Make changes
3. Submit a PR
4. Review & merge
> Using WSL with VS Code for development is recommended for consistent line endings on Windows.
---
## Writing Unit Tests
Follow this convention when writing unittests. **Implement the function first, then write tests.**
### Example: Phone Number Validation
**Function (`phone_validation.py`):**
```python
import re
def is_valid_phone_number(phone_number: str) -> bool:
"""Return True if phone_number matches '123-456-7890' format."""
return bool(re.search(r"^(\d{3}-){2}\d{4}$", phone_number))
```
**Unit Test (`test_phone_validation.py`):**
```python ```python
import unittest import unittest
from phone_validation import is_valid_phone_number
#<!-- The Function the User needs to write --> class TestPhoneNumberRegex(unittest.TestCase):
def revstring(x):
return x[::-1]
#<!-- This Test, test if the function works --> def test_if_valid(self):
class TestSolution(unittest.TestCase): test_cases = [
def test_simple(self): ("123-456-7890", True),
# !! This needs to be dynamic ; if the user enters some shit then it is supposed to work too ("111-222-3333", True),
x=""; ("abc-def-ghij", False),
self.assertEqual(revstring(x), x[::-1]) ("1234567890", False),
("123-45-67890", False),
("12-3456-7890", False),
("", False),
]
print("\nPHONE NUMBER VALIDATION TEST RESULTS")
for phone, expected in test_cases:
try:
actual = is_valid_phone_number(phone)
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: '{phone}' -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: '{phone}' -> Exception: {e}")
raise
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main(verbosity=2)
``` ```
### ✅ Unit Test Guidelines
1. **Class Naming:** `Test<FunctionOrModuleName>`
2. **Method Naming:** `test_<what_is_being_tested>`
3. **Use tuples** `(input, expected_output)` for test cases
4. Include **edge cases** (empty strings, wrong formats)
5. **Print results** clearly for easier debugging
6. **Catch exceptions** and display failing input before raising
### What has changed for ease of use:
If you want to really easily create or edit programs then you should look at the Qt Programm.
It basically acts as a "VsCode" of this platform. After editing / creating i would suggest you look over everything in a serious
editor. Its still realtively new.

11
requirements.txt Normal file
View File

@@ -0,0 +1,11 @@
Flask>=3.0
Flask-SQLAlchemy>=3.1
Flask-Caching>=2.3.1
Markdown>=3.6
MarkupSafe>=2.1
watchdog>=4.0
gunicorn>=23.0.0
waitress>=3.0.2
pygments>=2.19.2
pyqt6>=6.9.1
PyQt6-WebEngine>=6.9.0

25
run.bash Normal file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
set -e # exit if any command fails
# Ensure src/database directory exists
mkdir -p src/database
# Create virtual environment if it doesn't exist
if [ ! -d "venv" ]; then
python -m venv venv
fi
source venv/bin/activate
# Upgrade pip and install dependencies
pip install --upgrade pip
pip install -r requirements.txt
# Export environment variables
export FLASK_APP=src.app
export FLASK_ENV=production
# Run with Gunicorn
echo "Starting Flask app with Gunicorn..."
exec gunicorn -w 4 -b 0.0.0.0:8000 src.app:app

3
run.bat Normal file
View File

@@ -0,0 +1,3 @@
:: make db directory and then launch the server
md .\src\database
python -m waitress --listen=0.0.0.0:8000 src.app:app

731
src/JavaScript/script.js Normal file
View File

@@ -0,0 +1,731 @@
/**
* -------------------------------------------------------------------
* Please read as a Developer:
* @file script.js
* @author rattatwinko
* @description "This is the JavaScript "frontend" File for the website.
* This handleds nearly every frontend logic / interaction
* if you want to change this, then you should be cautious.
* This is a complete mess. And its too complex to refactor.
* Atleast for me.
* @license MIT
* You can freely modify this file and distribute it as you wish.
*
* @todo
* - [] Refactor the jeriatric piece of shit code.
* ------------------------------------------------------------------
* This is the stupid fucking JavaScript, i hate this so fucking much
* why the fuck does this need to exsits, idk.
*
* CHANGELOG:
* aug18@21:51-> pagination for leaderboard ; and some shit refactoring.
*/
document.addEventListener("DOMContentLoaded", () => {
"use strict";
// Utility functions
const utils = {
safeLocalStorage: {
getItem(key) {
try {
return localStorage.getItem(key);
} catch (e) {
console.warn("localStorage.getItem failed:", e);
return null;
}
},
setItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
console.warn("localStorage.setItem failed:", e);
return false;
}
}
},
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func.apply(this, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
throttle(func, limit) {
let inThrottle;
return function executedFunction(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
};
// Dark Mode Manager
class DarkModeManager {
constructor() {
this.darkModeToggle = document.getElementById("darkModeToggle");
this.html = document.documentElement;
this.init();
}
init() {
this.loadSavedPreference();
this.attachEventListeners();
}
loadSavedPreference() {
const savedDarkMode = utils.safeLocalStorage.getItem("darkMode");
if (
savedDarkMode === "true" ||
(savedDarkMode === null &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
this.html.classList.add("dark");
}
}
attachEventListeners() {
this.darkModeToggle?.addEventListener("click", () => {
this.html.classList.toggle("dark");
utils.safeLocalStorage.setItem("darkMode", this.html.classList.contains("dark"));
});
}
}
// Problem Manager
class ProblemManager {
constructor() {
this.problemSearch = document.getElementById("problemSearch");
this.problemsContainer = document.getElementById("problemsContainer");
this.problemsPagination = document.getElementById("problemsPagination");
this.problemsPrevBtn = document.getElementById("problemsPrevBtn");
this.problemsNextBtn = document.getElementById("problemsNextBtn");
this.problemsPaginationInfo = document.getElementById("problemsPaginationInfo");
this.difficultyFilter = document.getElementById("difficultyFilter");
this.sortProblems = document.getElementById("sortProblems");
this.allProblemItems = [];
this.filteredProblemItems = [];
this.currentPage = 1;
this.itemsPerPage = 5;
this.problemSort = { column: "alpha", direction: "asc" };
this.problemDescriptionPopover = null;
this.manifestCache = new Map();
this.init();
}
init() {
if (!this.problemsContainer) return;
this.initializeProblemItems();
this.attachEventListeners();
this.injectPopoverCSS();
this.attachProblemHoverEvents();
}
initializeProblemItems() {
this.allProblemItems = Array.from(
this.problemsContainer.querySelectorAll(".problem-item") || []
);
this.filteredProblemItems = this.allProblemItems.map(this.getProblemData);
this.updatePagination();
}
getProblemData = (item) => ({
element: item,
name: item.dataset.name?.toLowerCase() || "",
desc: item.dataset.desc?.toLowerCase() || "",
difficulty: item.dataset.difficulty || "",
});
updatePagination() {
const totalPages = Math.ceil(this.filteredProblemItems.length / this.itemsPerPage);
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
// Hide all items first
this.allProblemItems.forEach((item) => {
item.style.display = "none";
});
// Show current page items
this.filteredProblemItems.slice(startIndex, endIndex).forEach((item) => {
item.element.style.display = "";
});
// Update pagination controls
if (this.problemsPrevBtn) this.problemsPrevBtn.disabled = this.currentPage <= 1;
if (this.problemsNextBtn) this.problemsNextBtn.disabled = this.currentPage >= totalPages;
if (this.problemsPaginationInfo) {
this.problemsPaginationInfo.textContent =
totalPages > 0
? `Page ${this.currentPage} of ${totalPages}`
: "No problems found";
}
this.setupPaginationLayout();
}
setupPaginationLayout() {
if (this.problemsPagination) {
Object.assign(this.problemsPagination.style, {
display: "flex",
justifyContent: "center",
position: "absolute",
left: "0",
right: "0",
bottom: "0",
margin: "0 auto",
width: "100%",
background: "inherit",
borderTop: "1px solid var(--border)",
padding: "12px 0"
});
// Style the pagination buttons and info text
const prevBtn = this.problemsPagination.querySelector('#problemsPrevBtn');
const nextBtn = this.problemsPagination.querySelector('#problemsNextBtn');
const infoText = this.problemsPagination.querySelector('#problemsPaginationInfo');
if (prevBtn) prevBtn.style.marginRight = '10px';
if (nextBtn) nextBtn.style.marginLeft = '10px';
if (infoText) infoText.style.marginTop = '2px';
this.problemsPagination.classList.remove("hidden");
}
if (this.problemsContainer?.parentElement) {
Object.assign(this.problemsContainer.parentElement.style, {
position: "relative",
paddingBottom: "56px"
});
}
}
showProblemDescription = async (item) => {
this.hideProblemDescription();
const folder = item.querySelector('a')?.getAttribute('href')?.split('/').pop();
if (!folder) return;
try {
let manifest = this.manifestCache.get(folder);
if (!manifest) {
// Try localStorage cache first
const cacheKey = `problem_manifest_${folder}`;
const cached = utils.safeLocalStorage.getItem(cacheKey);
if (cached) {
manifest = JSON.parse(cached);
this.manifestCache.set(folder, manifest);
} else {
// Fetch from API
const response = await fetch(`/api/problem_manifest/${encodeURIComponent(folder)}`);
manifest = response.ok ? await response.json() : { description: 'No description.' };
this.manifestCache.set(folder, manifest);
utils.safeLocalStorage.setItem(cacheKey, JSON.stringify(manifest));
}
}
this.createPopover(manifest.description || 'No description.', item);
} catch (error) {
console.warn("Failed to load problem description:", error);
this.createPopover('No description available.', item);
}
};
createPopover(description, item) {
this.problemDescriptionPopover = document.createElement("div");
this.problemDescriptionPopover.className = "problem-desc-popover";
this.problemDescriptionPopover.textContent = description;
document.body.appendChild(this.problemDescriptionPopover);
const rect = item.getBoundingClientRect();
Object.assign(this.problemDescriptionPopover.style, {
position: "fixed",
left: `${rect.left + window.scrollX}px`,
top: `${rect.bottom + window.scrollY + 6}px`,
zIndex: "1000",
minWidth: `${rect.width}px`
});
}
hideProblemDescription = () => {
if (this.problemDescriptionPopover) {
this.problemDescriptionPopover.remove();
this.problemDescriptionPopover = null;
}
};
attachProblemHoverEvents() {
this.allProblemItems.forEach((item) => {
item.addEventListener("mouseenter", () => this.showProblemDescription(item));
item.addEventListener("mouseleave", this.hideProblemDescription);
item.addEventListener("mousemove", this.handleMouseMove);
});
}
handleMouseMove = utils.throttle((e) => {
if (this.problemDescriptionPopover) {
this.problemDescriptionPopover.style.left = `${e.clientX + 10}px`;
}
}, 16); // ~60fps
sortProblemItems(column, direction) {
this.filteredProblemItems.sort((a, b) => {
let valueA, valueB;
switch (column) {
case "alpha":
valueA = a.name;
valueB = b.name;
break;
case "difficulty":
const difficultyOrder = { easy: 1, medium: 2, hard: 3 };
valueA = difficultyOrder[a.difficulty] || 0;
valueB = difficultyOrder[b.difficulty] || 0;
break;
default:
return 0;
}
let comparison = 0;
if (typeof valueA === "number" && typeof valueB === "number") {
comparison = valueA - valueB;
} else {
comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
}
return direction === "asc" ? comparison : -comparison;
});
}
attachEventListeners() {
this.problemsPrevBtn?.addEventListener("click", () => {
if (this.currentPage > 1) {
this.currentPage--;
this.updatePagination();
}
});
this.problemsNextBtn?.addEventListener("click", () => {
const totalPages = Math.ceil(this.filteredProblemItems.length / this.itemsPerPage);
if (this.currentPage < totalPages) {
this.currentPage++;
this.updatePagination();
}
});
this.problemSearch?.addEventListener("input", utils.debounce(() => {
this.filterProblems();
this.currentPage = 1;
this.updatePagination();
}, 300));
this.difficultyFilter?.addEventListener("change", () => {
this.filterProblems();
this.currentPage = 1;
this.updatePagination();
});
this.sortProblems?.addEventListener("change", () => {
const value = this.sortProblems.value;
if (value === "alpha" || value === "difficulty") {
if (this.problemSort.column === value) {
this.problemSort.direction = this.problemSort.direction === "asc" ? "desc" : "asc";
} else {
this.problemSort.column = value;
this.problemSort.direction = "asc";
}
this.sortProblemItems(this.problemSort.column, this.problemSort.direction);
this.currentPage = 1;
this.updatePagination();
}
});
}
filterProblems() {
const searchTerm = (this.problemSearch?.value || "").toLowerCase().trim();
const difficulty = this.difficultyFilter?.value || "all";
this.filteredProblemItems = this.allProblemItems
.map(this.getProblemData)
.filter(item => {
const matchesSearch = !searchTerm ||
item.name.includes(searchTerm) ||
item.desc.includes(searchTerm);
const matchesDifficulty = difficulty === "all" ||
item.difficulty === difficulty;
return matchesSearch && matchesDifficulty;
});
}
injectPopoverCSS() {
if (document.getElementById("problem-desc-popover-style")) return;
const style = document.createElement("style");
style.id = "problem-desc-popover-style";
style.textContent = `
.problem-desc-popover {
background: var(--card, #fff);
color: var(--text, #222);
border: 1px solid var(--border, #e5e7eb);
border-radius: 8px;
box-shadow: 0 4px 16px rgba(16,24,40,0.13);
padding: 12px 16px;
font-size: 0.98rem;
max-width: 350px;
min-width: 180px;
pointer-events: none;
opacity: 0.97;
transition: opacity 0.2s;
word-break: break-word;
}
html.dark .problem-desc-popover {
background: var(--card, #1e293b);
color: var(--text, #f1f5f9);
border: 1px solid var(--border, #334155);
}
`;
document.head.appendChild(style);
}
destroy() {
// Clean up event listeners and resources
this.hideProblemDescription();
this.manifestCache.clear();
}
}
// Leaderboard Manager
class LeaderboardManager {
constructor() {
this.problemFilter = document.getElementById("problemFilter");
this.runtimeFilter = document.getElementById("runtimeFilter");
this.leaderboardBody = document.getElementById("leaderboardBody");
this.sortableHeaders = document.querySelectorAll(".sortable");
this.rankInfoBtn = document.getElementById("rankInfoBtn");
this.rankingExplanation = document.getElementById("rankingExplanation");
this.currentSort = { column: "rank", direction: "asc" };
this.allRows = [];
this.filteredRows = [];
this.currentPage = 1;
this.itemsPerPage = 5;
this.leaderboardPagination = document.createElement("div");
this.leaderboardPagination.className = "pagination-controls";
this.leaderboardPagination.style.display = "flex";
this.leaderboardPagination.style.justifyContent = "center";
this.leaderboardPagination.style.position = "absolute";
this.leaderboardPagination.style.left = 0;
this.leaderboardPagination.style.right = 0;
this.leaderboardPagination.style.bottom = 0;
this.leaderboardPagination.style.margin = "0 auto 0 auto";
this.leaderboardPagination.style.width = "100%";
this.leaderboardPagination.style.background = "inherit";
this.leaderboardPagination.style.borderTop = "1px solid var(--border)";
this.leaderboardPagination.style.padding = "12px 0";
this.leaderboardPagination.innerHTML = `
<button class="pagination-btn" id="leaderboardPrevBtn" disabled style="margin-right:10px;">← Previous</button>
<span class="pagination-info" id="leaderboardPaginationInfo" style="margin-top:2px;">Page 1 of 1</span>
<button class="pagination-btn" id="leaderboardNextBtn" disabled style="margin-left:10px;">Next →</button>
`;
this.leaderboardPrevBtn = this.leaderboardPagination.querySelector("#leaderboardPrevBtn");
this.leaderboardNextBtn = this.leaderboardPagination.querySelector("#leaderboardNextBtn");
this.leaderboardPaginationInfo = this.leaderboardPagination.querySelector("#leaderboardPaginationInfo");
this.init();
}
init() {
if (!this.leaderboardBody || this.leaderboardBody.children.length === 0) return;
this.initializeRows();
this.attachEventListeners();
this.filterLeaderboard();
this.setInitialSortIndicator();
// Insert pagination controls after leaderboard table
const leaderboardContainer = document.getElementById("leaderboardContainer");
if (leaderboardContainer && !leaderboardContainer.contains(this.leaderboardPagination)) {
leaderboardContainer.appendChild(this.leaderboardPagination);
// Ensure parent card is relatively positioned and has enough bottom padding
const leaderboardCard = leaderboardContainer.closest('.card');
if (leaderboardCard) {
leaderboardCard.style.position = "relative";
leaderboardCard.style.paddingBottom = "56px";
}
}
// Also ensure the parent card (section.card) contains the controls for correct layout
const leaderboardCard = leaderboardContainer?.closest('.card');
if (leaderboardCard && !leaderboardCard.contains(this.leaderboardPagination)) {
leaderboardCard.appendChild(this.leaderboardPagination);
}
}
initializeRows() {
this.allRows = Array.from(this.leaderboardBody.querySelectorAll("tr")).map((row, index) => ({
element: row,
user: row.dataset.user || "",
problem: row.dataset.problem || "",
runtime: parseFloat(row.dataset.runtime) || 0,
memory: parseFloat(row.dataset.memory) || 0,
timestamp: new Date(row.dataset.timestamp || Date.now()).getTime(),
language: row.dataset.language || "",
originalIndex: index,
}));
}
filterLeaderboard() {
const problemTerm = (this.problemFilter?.value || "").toLowerCase().trim();
const runtimeType = this.runtimeFilter?.value || "all";
// Filter rows
this.filteredRows = this.allRows.filter((rowData) => {
let visible = true;
if (problemTerm) {
visible = rowData.problem.toLowerCase().includes(problemTerm);
}
return visible;
});
// Apply runtime filter (best/worst per user per problem)
if (runtimeType === "best" || runtimeType === "worst") {
const userProblemGroups = {};
this.filteredRows.forEach((rowData) => {
const key = `${rowData.user}::${rowData.problem}`;
if (!userProblemGroups[key]) userProblemGroups[key] = [];
userProblemGroups[key].push(rowData);
});
this.filteredRows = Object.values(userProblemGroups).flatMap((group) => {
if (group.length <= 1) return group;
group.sort((a, b) => a.runtime - b.runtime);
const keepIndex = runtimeType === "best" ? 0 : group.length - 1;
return [group[keepIndex]];
});
}
this.currentPage = 1;
this.updateLeaderboardPagination();
}
updateLeaderboardPagination() {
const totalPages = Math.ceil(this.filteredRows.length / this.itemsPerPage) || 1;
if (this.currentPage > totalPages) this.currentPage = totalPages;
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
// Hide all rows first
this.allRows.forEach((rowData) => {
rowData.element.style.display = "none";
});
// Show only current page rows
this.filteredRows.slice(startIndex, endIndex).forEach((rowData) => {
rowData.element.style.display = "";
});
// Update pagination controls
if (this.leaderboardPrevBtn) this.leaderboardPrevBtn.disabled = this.currentPage <= 1;
if (this.leaderboardNextBtn) this.leaderboardNextBtn.disabled = this.currentPage >= totalPages;
if (this.leaderboardPaginationInfo) {
this.leaderboardPaginationInfo.textContent =
totalPages > 0 ? `Page ${this.currentPage} of ${totalPages}` : "No entries found";
}
// Always show and center pagination at the bottom of the leaderboard card
if (this.leaderboardPagination) {
this.leaderboardPagination.classList.remove("hidden");
this.leaderboardPagination.style.display = "flex";
this.leaderboardPagination.style.justifyContent = "center";
this.leaderboardPagination.style.position = "absolute";
this.leaderboardPagination.style.left = 0;
this.leaderboardPagination.style.right = 0;
this.leaderboardPagination.style.bottom = 0;
this.leaderboardPagination.style.margin = "0 auto 0 auto";
this.leaderboardPagination.style.width = "100%";
this.leaderboardPagination.style.background = "inherit";
this.leaderboardPagination.style.borderTop = "1px solid var(--border)";
this.leaderboardPagination.style.padding = "12px 0";
}
// Make sure the parent leaderboard card is relatively positioned
const leaderboardContainer = document.getElementById("leaderboardContainer");
if (leaderboardContainer && leaderboardContainer.parentElement) {
leaderboardContainer.parentElement.style.position = "relative";
leaderboardContainer.parentElement.style.paddingBottom = "56px";
}
// Recalculate ranks for visible rows
this.calculateOverallRanking();
}
calculateOverallRanking() {
// Only consider visible rows (current page)
const visibleRows = this.filteredRows.slice(
(this.currentPage - 1) * this.itemsPerPage,
(this.currentPage - 1) * this.itemsPerPage + this.itemsPerPage
);
if (visibleRows.length === 0) return;
// Group submissions by problem to find the best performance for each
const problemBests = {};
visibleRows.forEach((rowData) => {
const problem = rowData.problem;
if (!problemBests[problem]) {
problemBests[problem] = {
bestRuntime: Infinity,
bestMemory: Infinity,
};
}
problemBests[problem].bestRuntime = Math.min(
problemBests[problem].bestRuntime,
rowData.runtime
);
problemBests[problem].bestMemory = Math.min(
problemBests[problem].bestMemory,
rowData.memory
);
});
// Calculate normalized scores for each submission
visibleRows.forEach((rowData) => {
const problemBest = problemBests[rowData.problem];
const runtimeScore =
problemBest.bestRuntime > 0
? rowData.runtime / problemBest.bestRuntime
: 1;
const memoryScore =
problemBest.bestMemory > 0
? rowData.memory / problemBest.bestMemory
: 1;
rowData.overallScore = runtimeScore * 0.7 + memoryScore * 0.3;
});
// Sort by overall score (lower is better), then by timestamp (earlier is better for ties)
visibleRows.sort((a, b) => {
const scoreDiff = a.overallScore - b.overallScore;
if (Math.abs(scoreDiff) > 0.000001) return scoreDiff;
return a.timestamp - b.timestamp;
});
// Reorder DOM elements and update ranks
const fragment = document.createDocumentFragment();
visibleRows.forEach((rowData, index) => {
fragment.appendChild(rowData.element);
// Update rank cell
const rankCell = rowData.element.cells[0];
if (rankCell) rankCell.textContent = index + 1 + (this.currentPage - 1) * this.itemsPerPage;
// Update rank classes
rowData.element.className = rowData.element.className.replace(/\brank-\d+\b/g, "");
if (index === 0) rowData.element.classList.add("rank-1");
else if (index < 3) rowData.element.classList.add("rank-top3");
});
this.leaderboardBody.appendChild(fragment);
// this.updateRankClasses(); // Function does not exist, so remove this call
}
attachEventListeners() {
// Sorting event listeners
this.sortableHeaders.forEach((header) => {
header.addEventListener("click", () => {
const column = header.dataset.sort;
if (!column) return;
// Remove sorting classes from all headers
this.sortableHeaders.forEach((h) => h.classList.remove("sort-asc", "sort-desc"));
// Toggle sort direction
if (this.currentSort.column === column) {
this.currentSort.direction = this.currentSort.direction === "asc" ? "desc" : "asc";
} else {
this.currentSort.column = column;
this.currentSort.direction = "asc";
}
// Add sorting class to current header
header.classList.add(`sort-${this.currentSort.direction}`);
// Sort filteredRows
this.filteredRows.sort((a, b) => {
let valueA = a[column];
let valueB = b[column];
if (typeof valueA === "string") valueA = valueA.toLowerCase();
if (typeof valueB === "string") valueB = valueB.toLowerCase();
let comparison = 0;
if (typeof valueA === "number" && typeof valueB === "number") {
comparison = valueA - valueB;
} else {
comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
}
return this.currentSort.direction === "asc" ? comparison : -comparison;
});
this.currentPage = 1;
this.updateLeaderboardPagination();
});
});
// Filter event listeners with debouncing
this.problemFilter?.addEventListener("input", utils.debounce(() => {
this.filterLeaderboard();
}, 300));
this.runtimeFilter?.addEventListener("change", () => this.filterLeaderboard());
// Pagination event listeners
this.leaderboardPrevBtn?.addEventListener("click", () => {
if (this.currentPage > 1) {
this.currentPage--;
this.updateLeaderboardPagination();
}
});
this.leaderboardNextBtn?.addEventListener("click", () => {
const totalPages = Math.ceil(this.filteredRows.length / this.itemsPerPage) || 1;
if (this.currentPage < totalPages) {
this.currentPage++;
this.updateLeaderboardPagination();
}
});
// Rank info popout
this.rankInfoBtn?.addEventListener("click", (e) => {
e.preventDefault();
this.rankingExplanation?.classList.toggle("active");
this.rankInfoBtn?.classList.toggle("active");
});
// Close ranking explanation when clicking outside
document.addEventListener("click", (e) => {
if (
this.rankingExplanation?.classList.contains("active") &&
!this.rankingExplanation.contains(e.target) &&
!this.rankInfoBtn?.contains(e.target)
) {
this.rankingExplanation.classList.remove("active");
this.rankInfoBtn?.classList.remove("active");
}
});
}
setInitialSortIndicator() {
const defaultHeader = document.querySelector('[data-sort="rank"]');
if (defaultHeader) {
defaultHeader.classList.add("sort-asc");
}
}
}
// Initialize all managers
const darkModeManager = new DarkModeManager();
const problemManager = new ProblemManager();
const leaderboardManager = new LeaderboardManager();
// Apply dark mode to dynamically created elements
const applyDarkModeToElements = () => {
// Any additional dark mode styling for dynamically created elements can go here
};
// Watch for dark mode changes
const darkModeObserver = new MutationObserver(applyDarkModeToElements);
darkModeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
// Cleanup on page unload
window.addEventListener("beforeunload", () => {
problemManager.destroy();
darkModeObserver.disconnect();
});
});

View File

@@ -1,124 +1,166 @@
from markupsafe import Markup # API endpoint to get problem manifest (description) by folder
from flask import Flask, render_template, request, redirect, url_for, send_from_directory from markupsafe import Markup
import markdown as md from flask import Flask, render_template, request, redirect, url_for, send_from_directory, jsonify
from flask_caching import Cache
from models import db, Problem, Solution import markdown as md
from utils import run_code_against_tests import ast
from leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard from src.models import db, Problem, Solution
from src.utils import run_code_against_tests
from src.leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard
import os import os
## from problem_loader import load_problems_from_json, schedule_problem_reload from src.problem_scanner import start_problem_scanner
from problem_scanner import start_problem_scanner import sqlite3
import sqlite3 from pathlib import Path
from pathlib import Path
# Config cache
app = Flask(__name__) config = {
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' "DEBUG": True,
db.init_app(app) "CACHE_TYPE": "SimpleCache",
"CACHE_DEFAULT_TIMEOUT": 300
}
@app.before_request app = Flask(__name__)
def setup(): app.config.from_mapping(config)
db.create_all() cache = Cache(app)
create_leaderboard_table() # Ensure leaderboard table exists BASE_DIR = Path(__file__).parent
# Problems are now loaded from manifests by the background scanner. No need to load problems.json. app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{BASE_DIR / 'database' / 'db.sqlite3'}"
# Start the background thread to scan problems print(f"[ INFO ] : Using database URI: {app.config['SQLALCHEMY_DATABASE_URI']}")
start_problem_scanner()
db.init_app(app)
@app.route("/script.js")
def script(): @app.before_request
return send_from_directory("templates", "script.js") def setup():
db.create_all()
@app.route('/favicon.ico') create_leaderboard_table() # Ensure leaderboard table exists
def favicon(): # Problems are loaded from manifests by the background scanner ; running on a different thread. No need to load problems.json.
return send_from_directory("templates", "favicon", "favicon.ico")
# Start the background thread to scan problems
@app.route('/') start_problem_scanner()
def index():
db_path = Path(__file__).parent / 'problems.sqlite3' @app.route('/api/problem_manifest/<folder>')
conn = sqlite3.connect(db_path) def api_problem_manifest(folder):
c = conn.cursor() # Try to load manifest.json from the problem folder
c.execute('SELECT folder, description, test_code FROM problems') import json
problems = c.fetchall() manifest_path = BASE_DIR / 'problems' / folder / 'manifest.json'
conn.close() if not manifest_path.exists():
# Get leaderboard entries return jsonify({'error': 'Manifest not found'}), 404
leaderboard = get_leaderboard() try:
# Map folder to title for display with open(manifest_path, 'r', encoding='utf-8') as f:
problem_titles = {folder: folder.replace('_', ' ').title() for folder, _, _ in problems} manifest = json.load(f)
return render_template('index.html', problems=problems, leaderboard=leaderboard, problem_titles=problem_titles) return jsonify(manifest)
except Exception as e:
@app.route('/problem/new', methods=['GET', 'POST']) return jsonify({'error': str(e)}), 500
def new_problem():
if request.method == 'POST': # I introduce you to the fucking JavaScript shit routes, fuck javascripts
title = request.form['title'] @app.route('/JavaScript/<path:filename>')
description = request.form['description'] @cache.cached(timeout=300)
test_code = request.form['test_code'] def serve_js(filename):
problem = Problem(title=title, description=description, test_code=test_code) return send_from_directory('JavaScript', filename)
db.session.add(problem)
db.session.commit() @app.route("/script.js")
return redirect(url_for('index')) @cache.cached(timeout=300)
return render_template('new_problem.html') def script():
return send_from_directory("JavaScript", "script.js")
@app.route('/problem/<folder>', methods=['GET', 'POST'])
def view_problem(folder): @app.route('/favicon.ico')
db_path = Path(__file__).parent / 'problems.sqlite3' @cache.cached()
conn = sqlite3.connect(db_path) def favicon():
c = conn.cursor() return send_from_directory("templates", "favicon.ico")
c.execute('SELECT folder, description, test_code FROM problems WHERE folder = ?', (folder,))
row = c.fetchone() @app.route('/')
conn.close() @cache.cached(timeout=300)
if not row: def index():
return 'Problem not found', 404 db_path = Path(__file__).parent / 'database/problems.sqlite3'
problem = { conn = sqlite3.connect(db_path)
'folder': row[0], c = conn.cursor()
'description': row[1], #<!-- The query was fucked up so it fetched the fucking testcode -->
'test_code': row[2], c.execute('SELECT folder, description, test_code, difficulty FROM problems')
'title': row[0].replace('_', ' ').title() problems = c.fetchall()
} conn.close()
result = None # Get leaderboard entries
if request.method == 'POST': leaderboard = get_leaderboard()
user_code = request.form['user_code'] # Map folder to title for display
username = request.form.get('username', '').strip() or 'Anonymous' problem_titles = {folder: folder.replace('_', ' ').title() for folder, _, _, _ in problems}
import tracemalloc return render_template('index.html', problems=problems, leaderboard=leaderboard, problem_titles=problem_titles)
tracemalloc.start()
run_result = run_code_against_tests(user_code, problem['test_code']) @app.route('/problem/new', methods=['GET', 'POST'])
current, peak = tracemalloc.get_traced_memory() def new_problem():
tracemalloc.stop() if request.method == 'POST':
memory_used = peak // 1024 # in KB title = request.form['title']
# Try to get the last line number executed (even for successful runs) description = request.form['description']
line_number = None test_code = request.form['test_code']
import ast problem = Problem(title=title, description=description, test_code=test_code)
try: db.session.add(problem)
tree = ast.parse(user_code) db.session.commit()
# Find the highest line number in the AST (for multi-function/user code) return redirect(url_for('index'))
def get_max_lineno(node): return render_template('new_problem.html')
max_lineno = getattr(node, 'lineno', 0)
for child in ast.iter_child_nodes(node): @app.route('/problem/<folder>', methods=['GET', 'POST'])
max_lineno = max(max_lineno, get_max_lineno(child)) def view_problem(folder):
return max_lineno db_path = Path(__file__).parent / 'database/problems.sqlite3'
line_number = get_max_lineno(tree) conn = sqlite3.connect(db_path)
except Exception: c = conn.cursor()
pass c.execute('SELECT folder, description,test_code , difficulty FROM problems WHERE folder = ?', (folder,))
# If there was an error, try to get the error line number from the traceback row = c.fetchone()
if run_result['error']: conn.close()
tb = run_result['error']
import traceback if not row:
try: return 'Problem not found', 404
tb_lines = traceback.extract_tb(traceback.TracebackException.from_string(tb).stack)
if tb_lines: problem = {
line_number = tb_lines[-1].lineno 'folder': row[0],
except Exception: 'description': row[1],
pass 'difficulty': row[3], # now correct
log_leaderboard(username, problem['folder'], run_result['runtime'], memory_used, line_number) 'test_code': row[2], # now correct
result = run_result }
return render_template('problem.html', problem=problem, result=result)
result = None
@app.template_filter('markdown') if request.method == 'POST':
def markdown_filter(text): user_code = request.form['user_code']
return Markup(md.markdown(text or '', extensions=['extra', 'sane_lists'])) username = request.form.get('username', '').strip() or 'Anonymous'
import tracemalloc
if __name__ == '__main__': tracemalloc.start()
app.run(debug=True) 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
try:
tree = ast.parse(user_code)
# Find the highest line number in the AST (for multi-function/user code)
def get_max_lineno(node):
max_lineno = getattr(node, 'lineno', 0)
for child in ast.iter_child_nodes(node):
max_lineno = max(max_lineno, get_max_lineno(child))
return max_lineno
line_number = get_max_lineno(tree)
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
# ONLY log to leaderboard if the solution passed all tests
if run_result['passed']:
log_leaderboard(username, problem['folder'], run_result['runtime'], memory_used, line_number)
result = run_result
return render_template('problem.html', problem=problem, result=result)
@app.template_filter('markdown')
def markdown_filter(text):
return Markup(md.markdown(text or '', extensions=['extra', 'sane_lists']))
if __name__ == '__main__':
app.run(debug=True)

222
src/cache.py Normal file
View File

@@ -0,0 +1,222 @@
"""
High-performance in-memory caching module with LRU eviction policy.
"""
import time
from typing import Any, Callable, Optional, Dict, List, Tuple
import threading
import functools
import logging
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class CacheEntry:
"""Represents a single cache entry with metadata."""
__slots__ = ('value', 'timestamp', 'expires_at', 'hits')
def __init__(self, value: Any, timeout: int):
self.value = value
self.timestamp = time.time()
self.expires_at = self.timestamp + timeout
self.hits = 0
def is_expired(self) -> bool:
"""Check if the cache entry has expired."""
return time.time() >= self.expires_at
def hit(self) -> None:
"""Increment the hit counter."""
self.hits += 1
class FastMemoryCache:
"""
High-performance in-memory cache with LRU eviction policy.
Thread-safe and optimized for frequent reads.
"""
def __init__(self, max_size: int = 1000, default_timeout: int = 300):
"""
Initialize the cache.
Args:
max_size: Maximum number of items to store in cache
default_timeout: Default expiration time in seconds
"""
self.max_size = max_size
self.default_timeout = default_timeout
self._cache: Dict[str, CacheEntry] = {}
self._lock = threading.RLock()
self._hits = 0
self._misses = 0
self._evictions = 0
# Start background cleaner thread
self._cleaner_thread = threading.Thread(target=self._clean_expired, daemon=True)
self._cleaner_thread.start()
def get(self, key: str) -> Optional[Any]:
"""
Get a value from the cache.
Args:
key: Cache key
Returns:
Cached value or None if not found/expired
"""
with self._lock:
entry = self._cache.get(key)
if entry is None:
self._misses += 1
return None
if entry.is_expired():
del self._cache[key]
self._misses += 1
self._evictions += 1
return None
entry.hit()
self._hits += 1
return entry.value
def set(self, key: str, value: Any, timeout: Optional[int] = None) -> None:
"""
Set a value in the cache.
Args:
key: Cache key
value: Value to cache
timeout: Optional timeout in seconds (uses default if None)
"""
if timeout is None:
timeout = self.default_timeout
with self._lock:
# Evict if cache is full (LRU policy)
if len(self._cache) >= self.max_size and key not in self._cache:
self._evict_lru()
self._cache[key] = CacheEntry(value, timeout)
def delete(self, key: str) -> bool:
"""
Delete a key from the cache.
Args:
key: Cache key to delete
Returns:
True if key was deleted, False if not found
"""
with self._lock:
if key in self._cache:
del self._cache[key]
self._evictions += 1
return True
return False
def clear(self) -> None:
"""Clear all items from the cache."""
with self._lock:
self._cache.clear()
self._evictions += len(self._cache)
def _evict_lru(self) -> None:
"""Evict the least recently used item from the cache."""
if not self._cache:
return
# Find the entry with the fewest hits (simplified LRU)
lru_key = min(self._cache.keys(), key=lambda k: self._cache[k].hits)
del self._cache[lru_key]
self._evictions += 1
def _clean_expired(self) -> None:
"""Background thread to clean expired entries."""
while True:
time.sleep(60) # Clean every minute
with self._lock:
expired_keys = [
key for key, entry in self._cache.items()
if entry.is_expired()
]
for key in expired_keys:
del self._cache[key]
self._evictions += 1
if expired_keys:
logger.info(f"Cleaned {len(expired_keys)} expired cache entries")
def get_stats(self) -> Dict[str, Any]:
"""
Get cache statistics.
Returns:
Dictionary with cache statistics
"""
with self._lock:
return {
'size': len(self._cache),
'hits': self._hits,
'misses': self._misses,
'hit_ratio': self._hits / (self._hits + self._misses) if (self._hits + self._misses) > 0 else 0,
'evictions': self._evictions,
'max_size': self.max_size
}
def keys(self) -> List[str]:
"""Get all cache keys."""
with self._lock:
return list(self._cache.keys())
# Global cache instance
cache = FastMemoryCache(max_size=2000, default_timeout=300)
def cached(timeout: Optional[int] = None, unless: Optional[Callable] = None):
"""
Decorator for caching function results.
Args:
timeout: Cache timeout in seconds
unless: Callable that returns True to bypass cache
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Bypass cache if unless condition is met
if unless and unless():
return func(*args, **kwargs)
# Create cache key from function name and arguments
key_parts = [func.__module__, func.__name__]
key_parts.extend(str(arg) for arg in args)
key_parts.extend(f"{k}={v}" for k, v in sorted(kwargs.items()))
key = "|".join(key_parts)
# Try to get from cache
cached_result = cache.get(key)
if cached_result is not None:
logger.info(f"Cache hit for {func.__name__}")
return cached_result
# Call function and cache result
result = func(*args, **kwargs)
cache.set(key, result, timeout)
logger.info(f"Cache miss for {func.__name__}, caching result")
return result
return wrapper
return decorator
def cache_clear() -> None:
"""Clear the entire cache."""
cache.clear()
logger.info("Cache cleared")
def cache_stats() -> Dict[str, Any]:
"""Get cache statistics."""
return cache.get_stats()

View File

@@ -2,12 +2,14 @@ from flask_sqlalchemy import SQLAlchemy
from flask import g from flask import g
import os import os
import sqlite3 import sqlite3
from pathlib import Path
def get_db(): def get_db():
db = getattr(g, '_database', None) if 'db' not in g:
if db is None: db_path = Path(__file__).parent / 'database' / 'db.sqlite3'
db = g._database = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'db.sqlite3')) db_path.parent.mkdir(exist_ok=True) # Ensure /database folder exists
return db g.db = sqlite3.connect(db_path)
return g.db
def create_leaderboard_table(): def create_leaderboard_table():
db = get_db() db = get_db()

View File

@@ -1,376 +1,385 @@
import os import os
import time import time
import json import json
import sqlite3 import sqlite3
import threading import threading
import random import random
import tempfile import tempfile
import subprocess import subprocess
import sys import sys
import traceback import traceback
import io import io
from pathlib import Path from pathlib import Path
try: try:
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
WATCHDOG_AVAILABLE = True WATCHDOG_AVAILABLE = True
except ImportError: except ImportError:
WATCHDOG_AVAILABLE = False WATCHDOG_AVAILABLE = False
PROBLEMS_DIR = Path(__file__).parent / 'problems' PROBLEMS_DIR = Path(__file__).parent / 'problems'
DB_PATH = Path(__file__).parent / 'problems.sqlite3' DB_PATH = Path(__file__).parent / 'database/problems.sqlite3'
class ProblemScannerThread(threading.Thread): class ProblemScannerThread(threading.Thread):
def __init__(self, scan_interval=2): def __init__(self, scan_interval=2):
super().__init__(daemon=True) super().__init__(daemon=True)
self.scan_interval = scan_interval self.scan_interval = scan_interval
self.last_state = {} self.last_state = {}
self.observer = None self.observer = None
def create_table(self, conn): def create_table(self, conn):
c = conn.cursor() c = conn.cursor()
c.execute('PRAGMA journal_mode=WAL;') c.execute('PRAGMA journal_mode=WAL;')
c.execute('''CREATE TABLE IF NOT EXISTS problems ( c.execute('''CREATE TABLE IF NOT EXISTS problems (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
folder TEXT, folder TEXT,
description TEXT, description TEXT,
test_code TEXT difficulty TEXT,
)''') test_code TEXT
conn.commit() )''')
conn.commit()
def scan(self):
problems = [] def scan(self):
if not PROBLEMS_DIR.exists(): problems = []
print(f"Problems directory does not exist: {PROBLEMS_DIR}") if not PROBLEMS_DIR.exists():
return problems print(f"Problems directory does not exist: {PROBLEMS_DIR}")
return problems
for folder in PROBLEMS_DIR.iterdir():
if folder.is_dir(): for folder in PROBLEMS_DIR.iterdir():
# Dynamically find manifest file (manifest.json or manifets.json) if folder.is_dir():
manifest_path = None # Dynamically find manifest file (manifest.json or manifests.json)
for candidate in ["manifest.json", "manifets.json"]: manifest_path = None
candidate_path = folder / candidate for candidate in ["manifest.json", "manifests.json"]:
if candidate_path.exists(): candidate_path = folder / candidate
manifest_path = candidate_path if candidate_path.exists():
break manifest_path = candidate_path
break
desc_path = folder / 'description.md'
test_path = folder / 'test.py' desc_path = folder / 'description.md'
test_path = folder / 'test.py'
# Check if required files exist
if manifest_path and desc_path.exists() and test_path.exists(): # Check if required files exist
try: if manifest_path and desc_path.exists() and test_path.exists():
with open(desc_path, 'r', encoding='utf-8') as f: try:
description = f.read() with open(desc_path, 'r', encoding='utf-8') as f:
with open(test_path, 'r', encoding='utf-8') as f: description = f.read()
test_code = f.read() with open(test_path, 'r', encoding='utf-8') as f:
test_code = f.read()
problems.append({ with open(manifest_path, 'r', encoding='utf-8') as f:
'folder': folder.name, manifest = json.load(f)
'description': description,
'test_code': test_code difficulty = manifest.get('difficulty', 'unknown')
})
print(f"Found problem: {folder.name}") problems.append({
except Exception as e: 'folder': folder.name,
print(f"Error reading problem files for {folder.name}: {e}") 'description': description,
else: 'test_code': test_code,
missing_files = [] 'difficulty': difficulty
if not manifest_path: })
missing_files.append("manifest.json/manifets.json") print(f"[ INFO ]: Found problem: {folder.name} ; Difficulty: {difficulty}")
if not desc_path.exists(): except Exception as e:
missing_files.append("description.md") print(f"[ ERROR ]: Error reading problem files for {folder.name}: {e}")
if not test_path.exists(): else:
missing_files.append("test.py") missing_files = []
print(f"Skipping {folder.name}: missing {', '.join(missing_files)}") if not manifest_path:
missing_files.append("manifest.json/manifets.json")
print(f"Total problems found: {len(problems)}") if not desc_path.exists():
return problems missing_files.append("description.md")
if not test_path.exists():
def update_db(self, problems, retries=5): missing_files.append("test.py")
for attempt in range(retries): print(f"[ SKIP ]: Skipping {folder.name}: missing {', '.join(missing_files)}")
try:
conn = sqlite3.connect(DB_PATH, timeout=5) print(f"[ INFO ]: Total problems found: {len(problems)}")
c = conn.cursor() return problems
c.execute('PRAGMA journal_mode=WAL;')
def update_db(self, problems, retries=5):
# Clear existing problems for attempt in range(retries):
c.execute('DELETE FROM problems') try:
conn = sqlite3.connect(DB_PATH, timeout=5)
# Insert new problems c = conn.cursor()
for p in problems: c.execute('PRAGMA journal_mode=WAL;')
c.execute('INSERT INTO problems (folder, description, test_code) VALUES (?, ?, ?)',
(p['folder'], p['description'], p['test_code'])) # Clear existing problems
c.execute('DELETE FROM problems')
conn.commit()
print(f"Updated database with {len(problems)} problems") # Insert new problems
conn.close() for p in problems:
return c.execute('''INSERT INTO problems
(folder, description, difficulty, test_code)
except sqlite3.OperationalError as e: VALUES (?, ?, ?, ?)''',
if 'locked' in str(e).lower(): (p['folder'], p['description'], p['difficulty'], p['test_code']))
wait_time = 0.2 + random.random() * 0.3
print(f"Database locked, retrying in {wait_time:.2f}s (attempt {attempt + 1})") conn.commit()
time.sleep(wait_time) print(f"[ INFO ]: Updated database with {len(problems)} problems")
else: conn.close()
print(f"Database error: {e}") return
raise
except Exception as e: except sqlite3.OperationalError as e:
print(f"Unexpected error updating database: {e}") if 'locked' in str(e).lower():
raise wait_time = 0.2 + random.random() * 0.3
print(f"[ WARNING ]: Database locked, retrying in {wait_time:.2f}s (attempt {attempt + 1})")
print('Failed to update problems DB after several retries due to lock.') time.sleep(wait_time)
else:
def rescan_and_update(self): print(f"[ ERROR ]: Database error: {e}")
print("Scanning for problems...") raise
problems = self.scan() except Exception as e:
self.update_db(problems) print(f"[ ERROR ]: Unexpected error updating database: {e}")
raise
def run(self):
print("Starting problem scanner...") print('[ FATAL ERROR ]: Failed to update problems DB after several retries due to lock.')
# Initial scan and table creation def rescan_and_update(self):
try: print("[ INFO ]: Scanning for problems...")
conn = sqlite3.connect(DB_PATH) problems = self.scan()
self.create_table(conn) self.update_db(problems)
conn.close()
print("Database initialized") def run(self):
except Exception as e: print("[ INFO ]: Starting problem scanner...")
print(f"Failed to initialize database: {e}")
return # Initial scan and table creation
try:
# Initial scan conn = sqlite3.connect(DB_PATH)
self.rescan_and_update() self.create_table(conn)
conn.close()
if WATCHDOG_AVAILABLE: print("[ INFO ]: Database initialized")
print("Using watchdog for file monitoring") except Exception as e:
print(f"[ FATAL ERROR ]: Failed to initialize database: {e}")
class Handler(FileSystemEventHandler): return
def __init__(self, scanner):
self.scanner = scanner # Initial scan
self.last_event_time = 0 self.rescan_and_update()
def on_any_event(self, event): if WATCHDOG_AVAILABLE:
# Debounce events to avoid too many rescans print("[ INFO ]: Using watchdog for file monitoring")
now = time.time()
if now - self.last_event_time > 1: # Wait at least 1 second between rescans class Handler(FileSystemEventHandler):
self.last_event_time = now def __init__(self, scanner):
print(f"File system event: {event.event_type} - {event.src_path}") self.scanner = scanner
self.scanner.rescan_and_update() self.last_event_time = 0
event_handler = Handler(self) def on_any_event(self, event):
self.observer = Observer() # Debounce events to avoid too many rescans
self.observer.schedule(event_handler, str(PROBLEMS_DIR), recursive=True) now = time.time()
self.observer.start() if now - self.last_event_time > 1: # Wait at least 1 second between rescans
self.last_event_time = now
try: print(f"[ FSINFO ]: File system event: {event.event_type} - {event.src_path}")
while True: self.scanner.rescan_and_update()
time.sleep(1)
except KeyboardInterrupt: event_handler = Handler(self)
print("Stopping problem scanner...") self.observer = Observer()
finally: self.observer.schedule(event_handler, str(PROBLEMS_DIR), recursive=True)
self.observer.stop() self.observer.start()
self.observer.join()
else: try:
print(f"Watchdog not available, using polling every {self.scan_interval}s") while True:
# Fallback: poll every scan_interval seconds time.sleep(1)
try: except KeyboardInterrupt:
while True: print("[ KBINT_INFO ]: Stopping problem scanner...")
time.sleep(self.scan_interval) finally:
self.rescan_and_update() self.observer.stop()
except KeyboardInterrupt: self.observer.join()
print("Stopping problem scanner...") else:
print(f"[ WARNING ]: Watchdog not available, using polling every {self.scan_interval}s")
def start_problem_scanner(): # Fallback: poll every scan_interval seconds
scanner = ProblemScannerThread() try:
scanner.start() while True:
return scanner time.sleep(self.scan_interval)
self.rescan_and_update()
# Flask model loading functions except KeyboardInterrupt:
def load_problems_from_json(json_path): print("[ KBINT_INFO ]: Stopping problem scanner...")
"""Load problems from JSON file into Flask database"""
if not os.path.exists(json_path): def start_problem_scanner():
print(f"Problem JSON file not found: {json_path}") scanner = ProblemScannerThread()
return scanner.start()
return scanner
try:
with open(json_path, 'r', encoding='utf-8') as f: # Flask model loading functions
problems = json.load(f) def load_problems_from_json(json_path):
except Exception as e: """Load problems from JSON file into Flask database"""
print(f"Error reading JSON file: {e}") if not os.path.exists(json_path):
return print(f"[ DEPRECATED_INFO ]: Problem JSON file not found: {json_path}")
print("[ SUGGESTION ]: If you dont have this do not worry. Use mainfest.json!")
# This assumes you have imported the necessary Flask/SQLAlchemy components return
try:
from models import db, Problem try:
with open(json_path, 'r', encoding='utf-8') as f:
for p in problems: problems = json.load(f)
# Check if problem already exists by title except Exception as e:
existing = Problem.query.filter_by(title=p['title']).first() print(f"[ ERROR ]: Error reading JSON file: {e}")
return
# Load test code from solution file if provided
test_code = '' # This assumes you have imported the necessary Flask/SQLAlchemy components
if 'solution' in p and os.path.exists(p['solution']): try:
try: from models import db, Problem
with open(p['solution'], 'r', encoding='utf-8') as sf:
test_code = sf.read() for p in problems:
except Exception as e: # Check if problem already exists by title
print(f"Error reading solution file for {p['title']}: {e}") existing = Problem.query.filter_by(title=p['title']).first()
if existing: # Load test code from solution file if provided
existing.description = p['description'] test_code = ''
existing.test_code = test_code if 'solution' in p and os.path.exists(p['solution']):
print(f"Updated problem: {p['title']}") try:
else: with open(p['solution'], 'r', encoding='utf-8') as sf:
new_problem = Problem(title=p['title'], description=p['description'], test_code=test_code) test_code = sf.read()
db.session.add(new_problem) except Exception as e:
print(f"Added new problem: {p['title']}") print(f"[ FATAL ERROR ]: Error reading solution file for {p['title']}: {e}")
db.session.commit() if existing:
print("Successfully updated problems from JSON") existing.description = p['description']
existing.test_code = test_code
except ImportError: print(f"[ INFO ]: Updated problem: {p['title']}")
print("Flask models not available - skipping JSON load") else:
except Exception as e: new_problem = Problem(title=p['title'], description=p['description'], test_code=test_code)
print(f"Error loading problems from JSON: {e}") db.session.add(new_problem)
print(f"[ SUCCESS ]: Added new problem: {p['title']}")
def schedule_problem_reload(app, json_path, interval_hours=10):
"""Schedule periodic reloading of problems from JSON""" db.session.commit()
def reload_loop(): print("[ SUCCESS ]: Successfully updated problems from JSON")
while True:
try: except ImportError:
with app.app_context(): print("[ FATAL IMPORT ERROR ]: Flask models not available - skipping JSON load @execptImportError")
load_problems_from_json(json_path) except Exception as e:
time.sleep(interval_hours * 3600) print(f"[ ERROR ]: Error loading problems from JSON: {e}")
except Exception as e:
print(f"Error in problem reload loop: {e}") def schedule_problem_reload(app, json_path, interval_hours=10):
time.sleep(60) # Wait 1 minute before retrying """Schedule periodic reloading of problems from JSON"""
def reload_loop():
t = threading.Thread(target=reload_loop, daemon=True) while True:
t.start() try:
with app.app_context():
def run_code_against_tests(user_code, test_code, timeout=10): load_problems_from_json(json_path)
""" time.sleep(interval_hours * 3600)
Execute user code against test code with proper error handling. except Exception as e:
print(f"[ FATAL ERROR ]: Error in problem reload loop: {e}")
Args: time.sleep(60) # Wait 1 minute before retrying
user_code: The user's solution code
test_code: The test code to validate the solution t = threading.Thread(target=reload_loop, daemon=True)
timeout: Maximum execution time in seconds t.start()
Returns: def run_code_against_tests(user_code, test_code, timeout=10):
dict: Result with passed, output, runtime, and error fields """
""" Execute user code against test code with proper error handling.
if not user_code or not user_code.strip():
return { Args:
'passed': False, user_code: The user's solution code
'output': '', test_code: The test code to validate the solution
'runtime': 0, timeout: Maximum execution time in seconds
'error': 'No code provided'
} Returns:
dict: Result with passed, output, runtime, and error fields
if not test_code or not test_code.strip(): """
return { if not user_code or not user_code.strip():
'passed': False, return {
'output': '', 'passed': False,
'runtime': 0, 'output': '',
'error': 'No test code available' 'runtime': 0,
} 'error': 'No code provided'
}
start_time = time.perf_counter()
output = '' if not test_code or not test_code.strip():
error = None return {
passed = False 'passed': False,
temp_file = None 'output': '',
'runtime': 0,
try: 'error': 'No test code available'
# Check if unittest is used in test_code }
if 'unittest' in test_code:
# Create temporary file with user code + test code start_time = time.perf_counter()
with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False, encoding='utf-8') as f: output = ''
# Combine user code and test code error = None
combined_code = f"{user_code}\n\n{test_code}" passed = False
f.write(combined_code) temp_file = None
f.flush()
temp_file = f.name try:
# Check if unittest is used in test_code
try: if 'unittest' in test_code:
# Run the file as a subprocess with timeout # Create temporary file with user code + test code
proc = subprocess.run( with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False, encoding='utf-8') as f:
[sys.executable, temp_file], # Combine user code and test code
capture_output=True, combined_code = f"{user_code}\n\n{test_code}"
text=True, f.write(combined_code)
timeout=timeout, f.flush()
encoding='utf-8' temp_file = f.name
)
try:
output = proc.stdout # Run the file as a subprocess with timeout
if proc.stderr: proc = subprocess.run(
output += f"\nSTDERR:\n{proc.stderr}" [sys.executable, temp_file],
capture_output=True,
passed = proc.returncode == 0 text=True,
if not passed: timeout=timeout,
error = f"Tests failed. Return code: {proc.returncode}\n{output}" encoding='utf-8'
)
except subprocess.TimeoutExpired:
passed = False output = proc.stdout
error = f"Code execution timed out after {timeout} seconds" if proc.stderr:
output = "Execution timed out" output += f"\nSTDERR:\n{proc.stderr}"
else: passed = proc.returncode == 0
# Direct execution approach for simple assert-based tests if not passed:
local_ns = {} error = f"Tests failed. Return code: {proc.returncode}\n{output}"
# Capture stdout except subprocess.TimeoutExpired:
old_stdout = sys.stdout passed = False
captured_output = io.StringIO() error = f"Code execution timed out after {timeout} seconds"
sys.stdout = captured_output output = "Execution timed out"
try: else:
# Execute user code first # Direct execution approach for simple assert-based tests
exec(user_code, {}, local_ns) local_ns = {}
# Execute test code in the same namespace # Capture stdout
exec(test_code, local_ns, local_ns) old_stdout = sys.stdout
captured_output = io.StringIO()
# If we get here without exceptions, tests passed sys.stdout = captured_output
passed = True
try:
except AssertionError as e: # Execute user code first
passed = False exec(user_code, {}, local_ns)
error = f"Assertion failed: {str(e)}"
# Execute test code in the same namespace
except Exception as e: exec(test_code, local_ns, local_ns)
passed = False
error = f"Runtime error: {traceback.format_exc()}" # If we get here without exceptions, tests passed
passed = True
finally:
output = captured_output.getvalue() except AssertionError as e:
sys.stdout = old_stdout passed = False
error = f"Assertion failed: {str(e)}"
except Exception as e:
passed = False except Exception as e:
error = f"Execution error: {traceback.format_exc()}" passed = False
error = f"Runtime error: {traceback.format_exc()}"
finally:
# Clean up temporary file finally:
if temp_file and os.path.exists(temp_file): output = captured_output.getvalue()
try: sys.stdout = old_stdout
os.unlink(temp_file)
except Exception as e: except Exception as e:
print(f"Warning: Could not delete temp file {temp_file}: {e}") passed = False
error = f"Execution error: {traceback.format_exc()}"
runtime = time.perf_counter() - start_time
finally:
result = { # Clean up temporary file
'passed': passed, if temp_file and os.path.exists(temp_file):
'output': output.strip() if output else '', try:
'runtime': runtime, os.unlink(temp_file)
'error': error if not passed else None except Exception as e:
} print(f"[ FATAL WARNING ]: Could not delete temp file {temp_file}: {e}")
print(f"Test execution result: passed={passed}, runtime={runtime:.3f}s") runtime = time.perf_counter() - start_time
if error:
print(f"Error: {error}") result = {
'passed': passed,
return result 'output': output.strip() if output else '',
'runtime': runtime,
'error': error if not passed else None
}
print(f"[ TEST RESULT ]: passed={passed}, runtime={runtime:.3f}s")
if error:
print(f"Error: {error}")
return result

View File

@@ -0,0 +1,50 @@
## 🏷️ Problem: Lost & Found Office
You are designing a system for a **Lost-and-Found office**.
* People can **report lost items**, where each item is mapped to the owners name.
* People can later **claim their item**.
* If the item is not found, return `"No item found!"`.
---
### Function Signature
```python
class LostAndFound:
def __init__(self):
pass
def add_item(self, owner: str, item: str) -> None:
"""
Stores the item with the owner's name.
"""
def claim_item(self, owner: str) -> str:
"""
Returns the owner's item if it exists, otherwise
returns 'No item found!'.
"""
```
---
### Example
```python
office = LostAndFound()
office.add_item("Alice", "Umbrella")
office.add_item("Bob", "Backpack")
print(office.claim_item("Alice")) # Output: "Umbrella"
print(office.claim_item("Alice")) # Output: "No item found!"
print(office.claim_item("Charlie")) # Output: "No item found!"
```
---
### Constraints
* `1 <= len(owner), len(item) <= 100`
* You may assume only **strings** are used for owner and item.
* An owner can only have **one item** at a time.

View File

@@ -0,0 +1,7 @@
{
"title": "Hashmaps",
"description": "DSA - Hashmap. With Lost & Found",
"description_md": "problems/Hashmaps/description.md",
"test_code": "problems/Hashmaps/test.py",
"difficulty": "hard"
}

View File

@@ -0,0 +1,54 @@
#class LostAndFound:
# def __init__(self):
# self.items = {} # hashmap: owner -> item
#
# def add_item(self, owner: str, item: str) -> None:
# self.items[owner] = item
#
# def claim_item(self, owner: str) -> str:
# return self.items.pop(owner, "No item found!")
import unittest
class TestLostAndFound(unittest.TestCase):
def test_basic(self):
office = LostAndFound()
office.add_item("Alice", "Umbrella")
office.add_item("Bob", "Backpack")
test_cases = [
("Alice", "Umbrella", "First claim for Alice"),
("Alice", "No item found!", "Alice claims again (should fail)"),
("Charlie", "No item found!", "Charlie never added an item"),
]
print("\nTEST: Basic LostAndFound Behavior")
for name, expected, description in test_cases:
try:
actual = office.claim_item(name)
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | {description} | Input: {name} -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | {description} | Input: {name} -> Exception: {e}")
raise
def test_overwrite_item(self):
office = LostAndFound()
office.add_item("Bob", "Hat")
office.add_item("Bob", "Shoes") # overwrite
print("\nTEST: Overwriting Items")
try:
actual = office.claim_item("Bob")
expected = "Shoes"
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Overwritten item claim | Input: Bob -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Overwritten item claim | Input: Bob -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1,60 @@
## Problem: Check if a String is a Palindrome
Given a string `s`, determine whether it reads the same forward and backward.
Return `True` if it is a palindrome, otherwise return `False`.
A **palindrome** is a sequence of characters that is identical when reversed.
Comparison is **case-sensitive** and should consider all characters, including spaces and punctuation.
---
### Example 1
**Input:**
```
s = "racecar"
```
**Output:**
```
True
```
**Explanation:**
Reversing `"racecar"` results in `"racecar"`, which is the same as the original string.
---
### Example 2
**Input:**
```
s = "hello"
```
**Output:**
```
False
```
**Explanation:**
Reversing `"hello"` results in `"olleh"`, which is different from the original string.
---
### Constraints
* `0 <= len(s) <= 10^5`
* `s` may contain letters, digits, symbols, and spaces.
---
### Function Signature (Python)
```python
def palindrome(s: str) -> bool:
```

View File

@@ -0,0 +1,7 @@
{
"title": "Palindrome",
"description": "Find out wether or not a String is a Palindrome",
"description_md": "problems/Palindrome/description.md",
"test_code": "problems/Palindrome/test.py",
"difficulty": "medium"
}

View File

@@ -0,0 +1,32 @@
import unittest
#<!-- User expected Function -->
## def palindrome(s:str) -> bool:
## return s == s[::-1]
class TestSolution(unittest.TestCase):
def test_palindrome(self):
test_cases = [
("racecar", True), # Simple palindrome
("hello", False), # Not a palindrome
("", True), # Empty string
("a", True), # Single character
("madam", True), # Palindrome word
("Madam", False), # Case-sensitive check
("12321", True), # Numeric string palindrome
("123456", False), # Numeric string non-palindrome
]
print("\nFUNCTION OUTPUT TEST RESULTS")
for input_val, expected in test_cases:
try:
actual = palindrome(input_val) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: '{input_val}' -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: '{input_val}' -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1,90 @@
# Prime Number Function Checker
You are asked to **write a function** that checks if a number is a **prime number**.
### What is a Prime Number?
* A **prime number** is a whole number greater than `1`.
* It has only **two divisors**: `1` and the number itself.
* Example:
* `7` → Prime (divisible only by `1` and `7`)
* `8` → Not Prime (divisible by `1, 2, 4, 8`)
Numbers less than or equal to `1` are **not prime**.
📖 More info: [Wikipedia](https://en.wikipedia.org/wiki/Prime_number)
---
### Function Signature
```python
def check_prime(number: int) -> bool:
```
* **Input**:
* `number` → an integer
* **Output**:
* `True` → if the number is prime
* `False` → if the number is not prime
---
### Example 1
**Input:**
```python
check_prime(2)
```
**Output:**
```
True
```
---
### Example 2
**Input:**
```python
check_prime(4)
```
**Output:**
```
False
```
---
### Example 3
**Input:**
```python
check_prime(13)
```
**Output:**
```
True
```
---
**_Dont worry you do NOT need to write these Function Calls into your solution. QPP checks automatically_**
### Hint
Try using the **modulo operator `%`** to check if one number divides evenly into another.
If any number between `2` and `n-1` divides your number evenly, then its **not prime**.

View File

@@ -0,0 +1,7 @@
{
"title": "Prime Number Checker",
"description": "Determine if a given number is a prime number",
"description_md": "problems/PrimeNumber/description.md",
"test_code": "problems/PrimeNumber/test.py",
"difficulty": "medium"
}

View File

@@ -0,0 +1,33 @@
import unittest
# <!-- Function to check -->
# def check_prime(number : int) -> bool:
# for i in range(2, int(number)):
# if int(number) % i == 0:
# return False
# return True
class TestPrimeNumber(unittest.TestCase):
def test_prime_function(self):
test_cases = [
(2,True),
(3,True),
(4,False),
(6,False),
(1,False)
]
print("\nFUNCTION OUTPUT TEST RESULTS")
for input_val, expected in test_cases:
try:
actual = check_prime(input_val)
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: '{input_val}' -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: '{input_val}' -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1,44 @@
## Reverse a List
Write a function called `reverse_list` that takes a list as input and returns the list in reverse order.
You are **allowed** to just use Pythons built-in `.reverse()` method or slicing (`[::-1]`), try to reverse it manually for practice.
### Function Signature:
```python
def reverse_list(lst):
# your code here
```
### Requirements
* The function should return a new list with the elements in reversed order.
* The input list can contain:
* Numbers
* Strings
* Booleans
* A mix of different types
* Your function will be tested with:
* A small list (e.g., `[1, 2, 3]``[3, 2, 1]`)
* A longer list (e.g., `[1, 2, 3, 4]``[4, 3, 2, 1]`)
* An empty list (should return an empty list)
* A single-element list (should return the same list)
* A mixed-type list (e.g., `[1, 'a', True]``[True, 'a', 1]`)
### Example
```python
reverse_list([1, 2, 3])
# Output: [3, 2, 1]
reverse_list([])
# Output: []
reverse_list([5])
# Output: [5]
reverse_list([1, 'a', True])
# Output: [True, 'a', 1]
```

View File

@@ -0,0 +1,7 @@
{
"title": "Reversed List",
"description": "Given a list, return a new list with the elements in reverse order.",
"description_md": "problems/reversedlist/description.md",
"difficulty": "easy",
"test_code": "problems/reversedlist/test.py"
}

View File

@@ -0,0 +1,27 @@
import unittest
#def reverse_list(lst : list) -> list:
#return lst[::-1]
class TestSolution(unittest.TestCase):
def test_simple(self):
test_cases = [
([1, 2, 3], [3, 2, 1]), # Simple case
([1, 2, 3, 4], [4, 3, 2, 1]), # Longer list
([], []), # Empty list
([5], [5]), # Single element list
([1, 'a', True], [True, 'a', 1]) # Mixed types
]
print("\n FUNCTION OUTPUT TEST RESULTS")
for input_val , expected in test_cases:
try:
actual = reverse_list(input_val) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: {input_val} -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: {input_val} -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1,40 @@
## Fibonacci Number
Write a function called `fibonacci` that takes a non-negative integer `n` as input and returns the **n-th Fibonacci number**.
The Fibonacci sequence is defined as:
* `F(0) = 0`
* `F(1) = 1`
* `F(n) = F(n-1) + F(n-2)` for `n > 1`
### Function Signature:
```python
def fibonacci(n):
# return your solution
```
#### Requirements
* The function should return the `n`-th number in the Fibonacci sequence.
* If `n` is less than `0`, print `"Incorrect input"`.
* Your function will be tested with:
* Base cases (`n = 0` and `n = 1`)
* Small values of `n`
* Larger values of `n` (e.g., 9)
* Multiple test cases in sequence
#### Example:
```python
fibonacci(0) # returns 0
fibonacci(1) # returns 1
fibonacci(2) # returns 1
fibonacci(3) # returns 2
fibonacci(5) # returns 5
fibonacci(9) # returns 34
```
You can copy this into your problems solution description.

View File

@@ -0,0 +1,7 @@
{
"title": "Fibonacci Sequence",
"description": "Calculate the n-th Fibonacci number using a function. The Fibonacci sequence is defined as follows: F(0) = 0, F(1) = 1, and F(n) = F(n-1) + F(n-2) for n > 1.",
"description_md": "problems/fibonacisequence/description.md",
"difficulty": "medium",
"test_code": "problems/fibonacisequence/test.py"
}

View File

@@ -0,0 +1,52 @@
import unittest
class TestSolution(unittest.TestCase):
def test_simple(self):
test_cases = [
(0, 0), # Base case: n = 0
(1, 1), # Base case: n = 1
(2, 1), # Fibonacci(2) = 1
(3, 2), # Fibonacci(3) = 2
(5, 5), # Fibonacci(5) = 5
(9, 34), # Fibonacci(9) = 34
]
print("\n=== Function Output Test Results ===")
for input_val, expected in test_cases:
try:
actual = fibonacci(input_val) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: {input_val} -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: {input_val} -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)
"""
def fibonacci(n):
a = 0
b = 1
# Check if n is less than 0
if n < 0:
print("Incorrect input")
# Check if n is equal to 0
elif n == 0:
return 0
# Check if n is equal to 1
elif n == 1:
return b
else:
for i in range(1, n):
c = a + b
a = b
b = c
return b
print(fibonacci(9))
"""

View File

@@ -0,0 +1,58 @@
# Phone Number Regular Expression Validation
You are asked to write a function that checks if a given string is a valid phone number.
A valid phone number must follow this format:
```python
123-456-7890
```
* It contains **3 digits**, followed by a **dash (-)**
* Then another **3 digits**, followed by a **dash (-)**
* Then exactly **4 digits**
If the string matches this exact format, return **True**. Otherwise, return **False**.
---
### Example 1
```python
Input: "123-456-7890"
Output: True
```
### Example 2
```python
Input: "1234567890"
Output: False
```
### Example 3
```python
Input: "abc-def-ghij"
Output: False
```
---
### Function Signature
```python
import re
def is_valid_phone_number(phone_number: str) -> bool:
return bool("Your Solution Here!")
```
---
### Hint 🔑
* Use the **`re`** (regular expression) library.
* `\d` means “a digit” in regex.
* You will need exactly **3 digits**, then a dash, then **3 digits**, another dash, then **4 digits**.
* Anchors `^` (start of string) and `$` (end of string) can help ensure the whole string matches.

View File

@@ -0,0 +1,7 @@
{
"title": "Regex Phonenumber",
"description": "A regex problem to match phone numbers in various formats.",
"description_md": "problems/regex-phone/description.md",
"difficulty": "hard",
"test_code": "problems/regex-phone/test.py"
}

View File

@@ -0,0 +1,33 @@
import re
import unittest
## def is_valid_phone_number(phone_number : str):
## return bool(re.search(r"^(\d{3}-){2}\d{4}$", phone_number))
import unittest
class TestPhoneNumberRegex(unittest.TestCase):
def test_if_valid(self):
test_cases = [
("123-456-7890", True), # Valid format
("111-222-3333", True), # Another valid format
("abc-def-ghij", False), # Letters instead of digits
("1234567890", False), # Missing dashes
("123-45-67890", False), # Wrong grouping
("12-3456-7890", False), # Wrong grouping again
("", False), # Empty string
]
print("\nPHONE NUMBER VALIDATION TEST RESULTS")
for phone, expected in test_cases:
try:
actual = is_valid_phone_number(phone) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: '{phone}' -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: '{phone}' -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -2,5 +2,6 @@
"title":"Reversed String", "title":"Reversed String",
"description":"Reverse a String using a Function ; Try to write as little code as possible", "description":"Reverse a String using a Function ; Try to write as little code as possible",
"description_md":"problems/reversedstring/description.md", "description_md":"problems/reversedstring/description.md",
"difficulty":"easy",
"test_code":"problems/reversedstring/test.py" "test_code":"problems/reversedstring/test.py"
} }

View File

@@ -0,0 +1,14 @@
## Sorting a List
In this example you are given a Task: **Sort a List of _say_ Apples**.
## Function Signature:
```python
def sortlist(lst: list) -> list:
return # Your solution
```
Using the Type Inferrence gives you a Idea of what to return. You may freely choose to type or not to. The Python Interpreter does not care about Type Inferrence
Sorting manually may be tedious. Look at the [PyDocs](https://docs.python.org/3/howto/sorting.html#sorting-basics)

View File

@@ -2,5 +2,6 @@
"title": "Sort List", "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.", "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.",
"description_md": "problems/sortlist/description.md", "description_md": "problems/sortlist/description.md",
"difficulty": "easy",
"test_code": "problems/sortlist/test.py" "test_code": "problems/sortlist/test.py"
} }

View File

@@ -0,0 +1,26 @@
import unittest
#def sortlist(lst = [4,3,2,1]) -> list:
#return sorted(lst)
class TestSolution(unittest.TestCase):
def test_sort(self):
test_cases=[
([3,2,1],[1,2,3]),
([4,3,2,1],[1,2,3,4])
]
print("\n Function Output Test Results: ")
for input_val, expected in test_cases:
try:
actual = sortlist(input_val) # pyright: ignore[reportUndefinedVariable]
status = "PASS" if actual == expected else "FAIL"
print(f"{status} | Input: '{input_val}' -> Got: '{actual}' | Expected: '{expected}'")
self.assertEqual(actual,expected)
except Exception as e:
print(f"ERROR | Input: '{input_val}' -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

BIN
src/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

326
src/static/index.css vendored Normal file
View File

@@ -0,0 +1,326 @@
:root {
--bg: #f6f8fb;
--card: #fff;
--text: #0f172a;
--muted: #6b7280;
--accent: #2563eb;
--border: #e5e7eb;
--hover: #f3f4f6;
--shadow: 0 4px 12px rgba(16, 24, 40, 0.06);
--radius: 8px;
--mono: "JetBrains Mono", monospace;
}
/* Dark mode variables */
html.dark {
--bg: #0f172a;
--card: #1e293b;
--text: #f1f5f9;
--muted: #94a3b8;
--accent: #3b82f6;
--border: #334155;
--hover: #334155;
--shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
body {
font-family: Inter, sans-serif;
background: var(--bg);
color: var(--text);
padding: 16px;
display: flex;
justify-content: center;
align-items: center;
transition:
background-color 0.3s ease,
color 0.3s ease;
}
.wrap {
width: 100%;
max-width: 1100px;
}
header {
margin-bottom: 14px;
}
.header-content {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
header h1 {
text-align: center;
font-size: 1.6rem;
color: var(--text);
}
header p {
color: var(--muted);
font-size: 0.9rem;
}
.dark-mode-toggle {
position: absolute;
right: 0;
background: none;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 6px 10px;
cursor: pointer;
color: var(--text);
font-size: 1.2rem;
transition: all 0.3s ease;
}
.dark-mode-toggle:hover {
background: var(--hover);
transform: scale(1.05);
}
html.dark .dark-mode-icon::before {
content: "☀︎️";
}
html:not(.dark) .dark-mode-icon::before {
content: "⏾";
}
.dark-mode-icon {
display: inline-block;
}
.dark-mode-icon::before {
font-size: 1em;
}
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.content.single-column {
grid-template-columns: 1fr;
}
.card {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 12px;
}
/* Search/filter controls */
.search-controls {
margin-bottom: 12px;
display: flex;
gap: 8px;
}
.search-input {
flex: 1;
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 4px;
font-size: 0.9rem;
background: var(--card);
color: var(--text);
transition: border-color 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: var(--accent);
}
.filter-select {
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: 4px;
font-size: 0.9rem;
background: var(--card);
color: var(--text);
transition: border-color 0.3s ease;
}
.filter-select:focus {
outline: none;
border-color: var(--accent);
}
/* Problems list */
.problems-list .problem-item {
padding: 8px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s ease;
}
.problem-item:hover {
background: var(--hover);
}
.problem-item:last-child {
border-bottom: none;
}
.problem-item a {
text-decoration: none;
color: var(--accent);
font-weight: 600;
transition: color 0.3s ease;
}
.problem-item a:hover {
text-decoration: underline;
}
/* Difficulty badge */
.difficulty {
display: inline-flex;
align-items: center;
padding: 0.25em 0.6em;
border-radius: 10px;
font-size: 0.85em;
font-weight: bold;
text-transform: uppercase;
color: white;
white-space: nowrap;
}
.difficulty[data-difficulty="easy"] {
background-color: #4caf50; /* Green */
}
.difficulty[data-difficulty="medium"] {
background-color: #ffc107; /* Amber */
color: #333;
}
.difficulty[data-difficulty="hard"] {
background-color: #f44336; /* Red */
}
/* Leaderboard */
.leaderboard-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.leaderboard-controls {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.leaderboard-table th,
.leaderboard-table td {
padding: 6px 8px;
border-bottom: 1px solid var(--border);
text-align: left;
}
.leaderboard-table th {
background: var(--hover);
font-weight: 600;
color: var(--muted);
}
.leaderboard-table tr:hover {
background: var(--hover);
}
/* Sort indicators */
.sortable {
cursor: pointer;
position: relative;
padding-right: 16px;
}
.sortable::after {
content: "↕";
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 0.8em;
opacity: 0.5;
}
.sort-asc::after {
content: "↑";
opacity: 1;
}
.sort-desc::after {
content: "↓";
opacity: 1;
}
/* Toggle button */
.btn {
border: none;
background: transparent;
cursor: pointer;
color: var(--accent);
font-size: 0.85rem;
padding: 4px 6px;
border-radius: 4px;
}
.btn:hover {
background: rgba(37, 99, 235, 0.08);
}
.btn.active {
background: rgba(37, 99, 235, 0.15);
}
@media (max-width: 800px) {
.content {
grid-template-columns: 1fr;
}
.leaderboard-controls {
flex-direction: column;
}
}
/* Leaderboard horizontal collapse */
#leaderboardSection {
transition:
max-width 0.35s ease,
opacity 0.25s ease;
overflow: hidden;
max-width: 100%;
}
#leaderboardSection.hidden {
max-width: 0;
opacity: 0;
pointer-events: none;
}
#leaderboardSection.visible {
max-width: 100%; /* take full available space in grid column */
opacity: 1;
}
#rankingExplanation {
transition: all 0.35s ease;
}
/* Pagination Controls */
.pagination-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.pagination-btn {
background: var(--card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
color: var(--text);
font-size: 0.9rem;
transition: all 0.3s ease;
}
.pagination-btn:hover:not(:disabled) {
background: var(--hover);
border-color: var(--accent);
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-info {
color: var(--muted);
font-size: 0.9rem;
}
/* Hide pagination when not needed */
.pagination-controls.hidden {
display: none;
}

296
src/static/problem.css vendored Normal file
View File

@@ -0,0 +1,296 @@
:root {
--bg: #f9f9f9;
--card: #fff;
--text: #333;
--muted: #666;
--accent: #007bff;
--accent-hover: #0069d9;
--border: #eaeaea;
--hover: #f8f9fa;
--code-bg: #f6f8fa;
--editor-border: #ddd;
}
html.dark {
--bg: #0f172a;
--card: #1e293b;
--text: #f1f5f9;
--muted: #94a3b8;
--accent: #3b82f6;
--accent-hover: #2563eb;
--border: #334155;
--hover: #334155;
--code-bg: #1e293b;
--editor-border: #475569;
}
body {
font-family: "Inter", sans-serif;
margin: 0;
padding: 0;
background-color: var(--bg);
color: var(--text);
min-height: 100vh; /* allow content to grow */
overflow-y: auto; /* allow vertical scroll */
box-sizing: border-box;
transition:
background-color 0.3s ease,
color 0.3s ease;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
.main-container {
display: flex;
flex-wrap: wrap; /* wrap on small screens */
min-height: 100vh;
width: 100vw;
}
.problem-panel {
flex: 1 1 400px; /* grow/shrink with base 400px */
min-width: 300px;
background: var(--card);
overflow-y: auto;
padding: 20px;
border-right: 1px solid var(--border);
max-height: 100vh;
transition: background-color 0.3s ease;
}
.editor-container {
flex: 1 1 400px;
min-width: 300px;
display: flex;
flex-direction: column;
background: var(--card);
max-height: 100vh;
overflow: hidden; /* internal scroll handling */
transition: background-color 0.3s ease;
}
.editor-header {
padding: 15px 20px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.editor-wrapper {
flex: 1 1 auto;
display: flex;
flex-direction: column;
min-height: 0;
padding: 0 20px;
overflow-y: auto;
}
.problem-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.back-btn {
background: none;
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
font-size: 16px;
color: var(--muted);
margin-right: 15px;
padding: 6px 10px;
transition: all 0.3s ease;
}
.back-btn:hover {
color: var(--text);
background: var(--hover);
}
.dark-mode-toggle {
background: none;
border: 1px solid var(--border);
border-radius: 4px;
padding: 6px 10px;
cursor: pointer;
color: var(--text);
font-size: 1.2rem;
transition: all 0.3s ease;
}
.dark-mode-toggle:hover {
background: var(--hover);
transform: scale(1.05);
}
html.dark .dark-mode-icon::before {
content: "☀";
}
html:not(.dark) .dark-mode-icon::before {
content: "⏾";
}
.dark-mode-icon {
display: inline-block;
}
.dark-mode-icon::before {
font-size: 1em;
}
h1 {
font-size: 22px;
font-weight: 600;
margin: 0;
color: var(--text);
flex: 1;
}
.problem-desc {
line-height: 1.6;
font-size: 15px;
overflow-wrap: break-word;
}
.problem-desc pre {
background: var(--code-bg);
padding: 12px;
border-radius: 4px;
overflow-x: auto;
font-family: "JetBrains Mono", monospace;
font-size: 14px;
border: 1px solid var(--border);
}
.problem-desc code {
background: var(--code-bg);
padding: 2px 4px;
border-radius: 3px;
font-family: "JetBrains Mono", monospace;
font-size: 14px;
}
.editor-actions {
padding: 15px 0;
display: flex;
justify-content: flex-end;
flex-shrink: 0;
}
.editor-actions button {
background-color: var(--accent);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
font-size: 14px;
transition: background-color 0.3s ease;
}
.editor-actions button:hover {
background-color: var(--accent-hover);
}
#editor {
flex: 1 1 auto;
min-height: 300px;
border: 1px solid var(--editor-border);
border-radius: 4px;
overflow: auto;
max-height: 60vh;
}
.result-panel {
margin-top: 20px;
padding: 15px;
background: var(--hover);
border-radius: 4px;
margin-bottom: 20px;
min-height: 120px;
overflow-y: auto;
max-height: 30vh;
border: 1px solid var(--border);
transition: background-color 0.3s ease;
}
.result-panel h3 {
margin-top: 0;
font-size: 16px;
margin-bottom: 10px;
}
.result-panel pre {
background: var(--code-bg);
padding: 12px;
border-radius: 4px;
overflow-x: auto;
white-space: pre-wrap;
font-family: "JetBrains Mono", monospace;
font-size: 14px;
margin: 5px 0;
border: 1px solid var(--border);
}
.placeholder {
color: var(--muted);
font-style: italic;
text-align: center;
padding: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-size: 14px;
color: var(--muted);
}
input[type="text"] {
width: 100%;
padding: 8px;
border: 1px solid var(--border);
border-radius: 4px;
margin-bottom: 15px;
font-family: "Inter", sans-serif;
background: var(--card);
color: var(--text);
transition: border-color 0.3s ease;
}
input[type="text"]:focus {
outline: none;
border-color: var(--accent);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.main-container {
flex-direction: column;
height: auto;
overflow-y: visible;
}
.problem-panel,
.editor-container {
flex: none;
width: 100%;
min-width: auto;
max-height: none;
border-right: none;
border-bottom: 1px solid var(--border);
}
#editor {
min-height: 400px;
max-height: none;
}
.result-panel {
max-height: none;
}
}

View File

@@ -1,260 +1,317 @@
/* Reset and base styles */ :root {
* { --bg: #f8f9fa;
margin: 0; --card: #fff;
padding: 0; --text: #333;
box-sizing: border-box; --heading: #2c3e50;
} --heading-secondary: #34495e;
--accent: #3498db;
body { --accent-hover: #2980b9;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --success: #27ae60;
line-height: 1.6; --success-hover: #229954;
color: #333; --error: #e74c3c;
background-color: #f8f9fa; --muted: #6c757d;
padding: 20px; --muted-hover: #5a6268;
max-width: 1200px; --border: #ddd;
margin: 0 auto; --code-bg: #f4f4f4;
} --success-bg: #d4edda;
--success-text: #155724;
/* Main heading */ --error-bg: #f8d7da;
h1 { --error-text: #721c24;
color: #2c3e50; --hover-bg: #e3f2fd;
margin-bottom: -10px; --shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding-bottom: 3px; }
border-bottom: 3px solid #3498db;
font-size: 2.2em; html.dark {
} --bg: #0f172a;
--card: #1e293b;
h2 { --text: #f1f5f9;
color: #34495e; --heading: #3b82f6;
margin: 30px 0 20px 0; --heading-secondary: #94a3b8;
font-size: 1.5em; --accent: #3b82f6;
} --accent-hover: #2563eb;
--success: #22c55e;
h3 { --success-hover: #16a34a;
color: #34495e; --error: #ef4444;
margin: 25px 0 15px 0; --muted: #64748b;
font-size: 1.3em; --muted-hover: #475569;
} --border: #334155;
--code-bg: #1e293b;
/* Links and buttons */ --success-bg: #065f46;
a { --success-text: #d1fae5;
color: #3498db; --error-bg: #7f1d1d;
text-decoration: none; --error-text: #fecaca;
padding: 8px 16px; --hover-bg: #1e40af;
border-radius: 5px; --shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
transition: background-color 0.3s ease; }
}
/* Reset and base styles */
a:hover { * {
background-color: #e3f2fd; margin: 0;
text-decoration: none; padding: 0;
} box-sizing: border-box;
}
/* Primary action link (Submit New Problem) */
a[href="/problem/new"] { body {
background-color: #3498db; font-family:
color: white; -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 600; line-height: 1.6;
margin-bottom: 30px; color: var(--text);
display: inline-block; background-color: var(--bg);
padding: 12px 24px; padding: 20px;
border-radius: 8px; max-width: 1200px;
} margin: 0 auto;
transition:
a[href="/problem/new"]:hover { background-color 0.3s ease,
background-color: #2980b9; color 0.3s ease;
} }
/* Problem list */ /* Main heading */
ul { h1 {
list-style: none; color: var(--heading);
background: white; margin-bottom: -10px;
border-radius: 8px; padding-bottom: 3px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); border-bottom: 3px solid var(--accent);
padding: 25px; font-size: 2.2em;
margin: 20px 0; }
}
h2 {
li { color: var(--heading-secondary);
padding: 15px 0; margin: 30px 0 20px 0;
border-bottom: 1px solid #eee; font-size: 1.5em;
} }
li:last-child { h3 {
border-bottom: none; color: var(--heading-secondary);
} margin: 25px 0 15px 0;
font-size: 1.3em;
li a { }
display: block;
padding: 12px 20px; /* Links and buttons */
margin: -12px -20px; a {
border-radius: 6px; color: var(--accent);
font-size: 1.1em; text-decoration: none;
} padding: 8px 16px;
border-radius: 5px;
li a:hover { transition: background-color 0.3s ease;
background-color: #f8f9fa; }
transform: translateX(5px);
transition: all 0.2s ease; a:hover {
} background-color: var(--hover-bg);
text-decoration: none;
/* Problem page specific styles */ }
.problem-header {
display: flex; /* Primary action link (Submit New Problem) */
align-items: center; a[href="/problem/new"] {
margin-bottom: 30px; background-color: var(--accent);
gap: 20px; color: white;
} font-weight: 600;
margin-bottom: 30px;
.back-btn { display: inline-block;
background-color: #95a5a6; padding: 12px 24px;
color: white; border-radius: 8px;
border: none; }
padding: 10px 20px;
border-radius: 6px; a[href="/problem/new"]:hover {
cursor: pointer; background-color: var(--accent-hover);
font-size: 14px; }
font-weight: 500;
transition: background-color 0.3s ease; /* Problem list */
} ul {
list-style: none;
.back-btn:hover { background: var(--card);
background-color: #7f8c8d; border-radius: 8px;
} box-shadow: var(--shadow);
padding: 25px;
.problem-desc { margin: 20px 0;
background: white; transition: background-color 0.3s ease;
padding: 30px; }
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); li {
margin-bottom: 30px; padding: 15px 0;
font-size: 1.1em; border-bottom: 1px solid var(--border);
line-height: 1.7; }
}
li:last-child {
/* Editor section */ border-bottom: none;
.editor-section { }
background: white;
padding: 30px; li a {
border-radius: 8px; display: block;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 12px 20px;
margin-bottom: 30px; margin: -12px -20px;
} border-radius: 6px;
font-size: 1.1em;
#editor { }
border: 2px solid #ddd;
border-radius: 8px; li a:hover {
margin: 20px 0; background-color: var(--hover-bg);
height: 400px; transform: translateX(5px);
overflow: hidden; transition: all 0.2s ease;
} }
.editor-actions { /* Problem page specific styles */
margin-top: 20px; .problem-header {
text-align: right; display: flex;
} align-items: center;
margin-bottom: 30px;
form button[type="submit"] { gap: 20px;
background-color: #27ae60; }
color: white;
border: none; .back-btn {
padding: 12px 30px; background-color: var(--muted);
border-radius: 8px; color: white;
cursor: pointer; border: none;
font-size: 16px; padding: 10px 20px;
font-weight: 600; border-radius: 6px;
transition: background-color 0.3s ease; cursor: pointer;
} font-size: 14px;
font-weight: 500;
form button[type="submit"]:hover { transition: background-color 0.3s ease;
background-color: #229954; }
}
.back-btn:hover {
/* Results section */ background-color: var(--muted-hover);
b { }
color: #2c3e50;
display: inline-block; .problem-desc {
margin: 10px 0 5px 0; background: var(--card);
} padding: 30px;
border-radius: 8px;
pre { box-shadow: var(--shadow);
background-color: #f4f4f4; margin-bottom: 30px;
padding: 20px; font-size: 1.1em;
border-radius: 6px; line-height: 1.7;
border-left: 4px solid #3498db; transition: background-color 0.3s ease;
margin: 10px 0 20px 0; }
overflow-x: auto;
font-family: 'JetBrains Mono', 'Courier New', monospace; /* Editor section */
font-size: 14px; .editor-section {
line-height: 1.4; background: var(--card);
} padding: 30px;
border-radius: 8px;
pre[style*="color:red"] { box-shadow: var(--shadow);
border-left-color: #e74c3c; margin-bottom: 30px;
background-color: #fdf2f2; transition: background-color 0.3s ease;
} }
/* Status messages */ #editor {
p[style*="color:green"] { border: 2px solid var(--border);
background-color: #d4edda; border-radius: 8px;
color: #155724; margin: 20px 0;
padding: 15px 20px; height: 400px;
border-radius: 6px; overflow: hidden;
border-left: 4px solid #27ae60; }
margin: 20px 0;
font-weight: 600; .editor-actions {
} margin-top: 20px;
text-align: right;
p[style*="color:red"] { }
background-color: #f8d7da;
color: #721c24; form button[type="submit"] {
padding: 15px 20px; background-color: var(--success);
border-radius: 6px; color: white;
border-left: 4px solid #e74c3c; border: none;
margin: 20px 0; padding: 12px 30px;
font-weight: 600; border-radius: 8px;
} cursor: pointer;
font-size: 16px;
/* Back to Problems link */ font-weight: 600;
a[href="/"] { transition: background-color 0.3s ease;
display: inline-block; }
margin-top: 30px;
background-color: #6c757d; form button[type="submit"]:hover {
color: white; background-color: var(--success-hover);
padding: 10px 20px; }
border-radius: 6px;
font-weight: 500; /* Results section */
} b {
color: var(--heading);
a[href="/"]:hover { display: inline-block;
background-color: #5a6268; margin: 10px 0 5px 0;
} }
/* Responsive design */ pre {
@media (max-width: 768px) { background-color: var(--code-bg);
body { padding: 20px;
padding: 15px; border-radius: 6px;
} border-left: 4px solid var(--accent);
margin: 10px 0 20px 0;
.problem-header { overflow-x: auto;
flex-direction: column; font-family: "JetBrains Mono", "Courier New", monospace;
align-items: flex-start; font-size: 14px;
gap: 15px; line-height: 1.4;
} border: 1px solid var(--border);
transition: background-color 0.3s ease;
h1 { }
font-size: 1.8em;
} pre[style*="color:red"] {
border-left-color: var(--error);
.problem-desc, .editor-section, ul { background-color: var(--error-bg);
padding: 20px; }
}
/* Status messages */
#editor { p[style*="color:green"] {
height: 300px; background-color: var(--success-bg);
} color: var(--success-text);
padding: 15px 20px;
.editor-actions { border-radius: 6px;
text-align: center; border-left: 4px solid var(--success);
} margin: 20px 0;
} font-weight: 600;
}
p[style*="color:red"] {
background-color: var(--error-bg);
color: var(--error-text);
padding: 15px 20px;
border-radius: 6px;
border-left: 4px solid var(--error);
margin: 20px 0;
font-weight: 600;
}
/* Back to Problems link */
a[href="/"] {
display: inline-block;
margin-top: 30px;
background-color: var(--muted);
color: white;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
}
a[href="/"]:hover {
background-color: var(--muted-hover);
}
/* 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;
}
}

166
src/templates/index.html Normal file
View File

@@ -0,0 +1,166 @@
<!doctype html>
<html lang="en" class="">
<head>
<script>
/*
This fix is for the fucking epileptics. idk what they're called.
It fixes the fucking flashing between changing pages.
This is both in the problems and this file
---
This changes nothing if the user uses light-system
*/
(function() {
try {
var dark = localStorage.getItem("darkMode");
if (
dark === "true" ||
(dark === null && window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
}
} catch (e) {}
})();
</script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Quick Problem Platform</title>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
</head>
<style>
/* Popout explanation */
#rankingExplanation {
grid-column: 1 / span 2;
max-height: 0;
opacity: 0;
overflow: hidden;
transition: max-height 0.5s ease, opacity 0.4s ease, padding 0.4s ease;
padding: 0 12px;
}
#rankingExplanation.active {
max-height: 800px;
opacity: 1;
padding: 12px;
}
#rankInfoBtn.active { color: #2563eb; cursor:pointer; transition: transform 0.3s ease; }
#rankInfoBtn.active { transform: rotate(90deg); }
/* Highlight top rank */
.rank-1 td:first-child { font-weight: bold; }
.sort-asc::after { content: " ↑"; }
.sort-desc::after { content: " ↓"; }
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="header-content">
<h1>Quick Problem Platform</h1>
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle dark mode">
<span class="dark-mode-icon"></span>
</button>
</div>
</header>
<div class="content" id="contentContainer">
<!-- Problems -->
<section class="card problems-list">
<div class="search-controls">
<input type="text" class="search-input" id="problemSearch" placeholder="Search problems..." />
</div>
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
<div id="problemsContainer">
{% for folder, description, test_code, difficulty in problems %}
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}" data-difficulty="{{ difficulty|lower }}">
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
<span class="difficulty" data-difficulty="{{ difficulty|lower }}">{{ difficulty }}</span>
</div>
{% else %}
<div class="problem-item">No problems yet.</div>
{% endfor %}
</div>
<div class="pagination-controls" id="problemsPagination">
<button class="pagination-btn" id="problemsPrevBtn" disabled>← Previous</button>
<span class="pagination-info" id="problemsPaginationInfo">Page 1 of 1</span>
<button class="pagination-btn" id="problemsNextBtn" disabled>Next →</button>
</div>
</section>
<!-- Leaderboard -->
<section class="card" id="leaderboardSection">
<div class="leaderboard-head">
<h2 style="font-size:1.1rem;margin:0">Leaderboard
<!--<span id="rankInfoBtn" title="How ranking works"></span>-->
</h2>
</div>
<div class="leaderboard-controls">
<input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." />
<select class="filter-select" id="runtimeFilter">
<option value="">All runtimes</option>
<option value="best">Best runtime</option>
<option value="worst">Worst runtime</option>
</select>
</div>
<div id="leaderboardContainer">
<table class="leaderboard-table">
<thead>
<tr>
<th class="sortable" data-sort="rank">Rank</th>
<th class="sortable" data-sort="user">User</th>
<th class="sortable" data-sort="problem">Problem</th>
<th class="sortable" data-sort="runtime">Runtime (s)</th>
<th class="sortable" data-sort="memory">Memory (KB)</th>
<th>Line</th>
<th class="sortable" data-sort="timestamp">Timestamp</th>
</tr>
</thead>
<tbody id="leaderboardBody">
{% for entry in leaderboard %}
<tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}"
data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}"
data-timestamp="{{ entry[5] }}">
<td>{{ loop.index }}</td>
<td>{{ entry[0] }}</td>
<td>
<a href="/problem/{{ problem_titles.get(entry[1], 'Unknown') }}"
style="color:#2563eb; text-decoration: none;"
onmouseover="this.style.textDecoration='underline';"
onmouseout="this.style.textDecoration='none';">
{{ problem_titles.get(entry[1], 'Unknown') }}
</a>
</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 %}
</tbody>
</table>
</div>
</section>
<!-- Ranking explanation -->
<section class="card" id="rankingExplanation">
<h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2>
<p>The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank:</p>
<ul style="margin-left: 15px;">
<li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li>
<li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li>
</ul>
<p>Overall score is calculated as:</p>
<div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;">
<code>
runtimeScore = yourRuntime / bestRuntime<br>
memoryScore = yourMemory / bestMemory<br>
overallScore = runtimeScore × 0.7 + memoryScore × 0.3
</code>
</div>
<p>Lower overall scores are better. If scores are equal, earlier submission ranks higher.</p>
</section>
</div>
</div>
<script src="{{ url_for('serve_js', filename='script.js') }}"></script>
</body>
</html>

178
src/templates/problem.html Normal file
View File

@@ -0,0 +1,178 @@
<!doctype html>
<html lang="en" class="">
<head>
<script>
/*
This fix is for the fucking epileptics. idk what they're called.
It fixes the fucking flashing between changing pages.
This is both in the index and this file
---
This changes nothing if the user uses light-system
*/
(function() {
try {
var dark = localStorage.getItem("darkMode");
if (
dark === "true" ||
(dark === null && window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
}
} catch (e) {}
})();
</script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ problem.title }} - Coding Problem</title>
<link rel="stylesheet" href="/static/style.css" />
<link rel="stylesheet" href="/static/problem.css" />
<!-- this is stoopid fucking html link for favicon. just cause of flask-->
<link
rel="icon"
type="image/x-icon"
href="{{ url_for('static', filename='favicon.ico') }}"
/>
<link
href="https://fonts.cdnfonts.com/css/jetbrains-mono"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div class="main-container">
<div class="problem-panel">
<div class="problem-header">
<button class="back-btn" onclick="window.location.href='/'">
← Back
</button>
<h1>{{ problem.title }}</h1>
<button
id="darkModeToggle"
class="dark-mode-toggle"
title="Toggle dark mode"
>
<span class="dark-mode-icon"></span>
</button>
</div>
<div class="problem-desc">
{{ problem.description | safe | markdown }}
</div>
</div>
<div class="editor-container">
<div class="editor-header">
<h2 style="margin: 0; font-size: 18px">
Submit Your Solution (Python)
</h2>
</div>
<div class="editor-wrapper">
<form method="post">
<label for="username">Username (optional):</label>
<input
type="text"
name="username"
id="username"
placeholder="Anonymous"
/>
<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 class="result-panel">
<h3>Result</h3>
{% if result %}
<p>
<b>Runtime:</b> {{ '%.4f'|format(result.runtime) }}
seconds
</p>
<p><b>Output:</b></p>
<pre>{{ result.output }}</pre>
{% if result.error %}
<p><b>Error:</b></p>
<pre>{{ result.error }}</pre>
{% endif %} {% else %}
<div class="placeholder">
Your code execution results will appear here
</div>
{% endif %}
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script>
// Dark mode functionality
const darkModeToggle = document.getElementById("darkModeToggle");
const html = document.documentElement;
// Load saved dark mode preference
const savedDarkMode = localStorage.getItem("darkMode");
if (
savedDarkMode === "true" ||
(savedDarkMode === null &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
html.classList.add("dark");
}
darkModeToggle.addEventListener("click", () => {
html.classList.toggle("dark");
localStorage.setItem(
"darkMode",
html.classList.contains("dark"),
);
// Update Monaco editor theme
if (window.monacoEditor) {
const isDark = html.classList.contains("dark");
window.monacoEditor.updateOptions({
theme: isDark ? "vs-dark" : "vs-light",
});
}
});
require.config({
paths: {
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs",
},
});
require(["vs/editor/editor.main"], function () {
const isDark = html.classList.contains("dark");
window.monacoEditor = monaco.editor.create(
document.getElementById("editor"),
{
value: "",
language: "python",
theme: isDark ? "vs-dark" : "vs-light",
fontFamily: "JetBrains Mono, monospace",
fontLigatures: true,
automaticLayout: true,
fontSize: 16,
minimap: { enabled: false },
},
);
document
.querySelector("form")
.addEventListener("submit", function (e) {
var code = window.monacoEditor.getValue();
if (!code.trim()) {
alert("Please enter your code before submitting.");
e.preventDefault();
return false;
}
document.getElementById("user_code").value = code;
});
});
</script>
</body>
</html>

591
src/utils.py Normal file
View File

@@ -0,0 +1,591 @@
import sys
import traceback
import time
import io
import tempfile
import subprocess
import os
import re
import ast
import signal
import resource
import shlex
import hashlib
import platform
from contextlib import contextmanager
# Security configuration - Expanded whitelist
ALLOWED_IMPORTS = {
'math', 'random', 'datetime', 'json', 'collections', 'itertools',
'functools', 'operator', 'copy', 'unittest', 're', 'string', 'pyfiglet',
'decimal', 'fractions', 'statistics', 'textwrap', 'unicodedata',
'base64', 'binascii', 'struct', 'array', 'heapq', 'bisect'
}
# Enhanced dangerous patterns with more comprehensive coverage
DANGEROUS_PATTERNS = [
# System/OS operations
r'import\s+os(?:\s|$|\.)', r'from\s+os\s+import',
r'import\s+subprocess(?:\s|$|\.)', r'from\s+subprocess\s+import',
r'import\s+sys(?:\s|$|\.)', r'from\s+sys\s+import',
r'import\s+shutil(?:\s|$|\.)', r'from\s+shutil\s+import',
r'import\s+pathlib(?:\s|$|\.)', r'from\s+pathlib\s+import',
r'import\s+tempfile(?:\s|$|\.)', r'from\s+tempfile\s+import',
r'import\s+glob(?:\s|$|\.)', r'from\s+glob\s+import',
r'import\s+platform(?:\s|$|\.)', r'from\s+platform\s+import',
# Network operations
r'import\s+socket(?:\s|$|\.)', r'from\s+socket\s+import',
r'import\s+urllib(?:\s|$|\.)', r'from\s+urllib\s+import',
r'import\s+requests(?:\s|$|\.)', r'from\s+requests\s+import',
r'import\s+http(?:\s|$|\.)', r'from\s+http\s+import',
r'import\s+ftplib(?:\s|$|\.)', r'from\s+ftplib\s+import',
r'import\s+smtplib(?:\s|$|\.)', r'from\s+smtplib\s+import',
# Dynamic execution
r'__import__\s*\(', r'exec\s*\(', r'eval\s*\(', r'compile\s*\(',
r'globals\s*\(', r'locals\s*\(', r'vars\s*\(', r'dir\s*\(',
r'getattr\s*\(', r'setattr\s*\(', r'delattr\s*\(', r'hasattr\s*\(',
# File operations
r'open\s*\(', r'file\s*\(', r'input\s*\(', r'raw_input\s*\(',
# Destructive operations
r'\.unlink\s*\(', r'\.remove\s*\(', r'\.rmdir\s*\(', r'\.rmtree\s*\(',
r'\.delete\s*\(', r'\.kill\s*\(', r'\.terminate\s*\(',
# Threading and multiprocessing
r'import\s+threading(?:\s|$|\.)', r'from\s+threading\s+import',
r'import\s+multiprocessing(?:\s|$|\.)', r'from\s+multiprocessing\s+import',
r'import\s+asyncio(?:\s|$|\.)', r'from\s+asyncio\s+import',
# Memory and resource manipulation
r'import\s+gc(?:\s|$|\.)', r'from\s+gc\s+import',
r'import\s+resource(?:\s|$|\.)', r'from\s+resource\s+import',
r'import\s+ctypes(?:\s|$|\.)', r'from\s+ctypes\s+import',
# Code introspection
r'import\s+inspect(?:\s|$|\.)', r'from\s+inspect\s+import',
r'import\s+types(?:\s|$|\.)', r'from\s+types\s+import',
# Pickle and serialization security risks
r'import\s+pickle(?:\s|$|\.)', r'from\s+pickle\s+import',
r'import\s+marshal(?:\s|$|\.)', r'from\s+marshal\s+import',
# System exit
r'exit\s*\(', r'quit\s*\(', r'sys\.exit\s*\(',
# Dunder methods are dangerous if misused, for us we allow classes
# specifically the constructor
# del i dont allow tho
r'__del__\s*\(',
# Import tricks
r'importlib', r'imp\s', r'pkgutil',
]
# Maximum resource limits
MAX_MEMORY_MB = 100 # 100MB memory limit
MAX_CPU_TIME = 5 # 5 seconds CPU time
MAX_OUTPUT_SIZE = 10000 # 10KB output limit
MAX_CODE_SIZE = 50000 # 50KB code limit
MAX_TEST_SIZE = 10000 # 10KB test limit
class SecurityViolationError(Exception):
"""Raised when a security violation is detected."""
pass
class ResourceLimitError(Exception):
"""Raised when resource limits are exceeded."""
pass
@contextmanager
def resource_limits():
"""Context manager to set resource limits."""
# Set memory limit (in bytes)
if hasattr(resource, 'RLIMIT_AS'):
try:
resource.setrlimit(resource.RLIMIT_AS, (MAX_MEMORY_MB * 1024 * 1024, MAX_MEMORY_MB * 1024 * 1024))
except (OSError, ValueError):
pass # Ignore if we can't set memory limits
# Set CPU time limit
if hasattr(resource, 'RLIMIT_CPU'):
try:
resource.setrlimit(resource.RLIMIT_CPU, (MAX_CPU_TIME, MAX_CPU_TIME))
except (OSError, ValueError):
pass # Ignore if we can't set CPU limits
# Set file descriptor limit
if hasattr(resource, 'RLIMIT_NOFILE'):
try:
resource.setrlimit(resource.RLIMIT_NOFILE, (10, 10))
except (OSError, ValueError):
pass
try:
yield
finally:
# Reset limits (though this won't matter much in subprocess)
pass
def validate_code_security(code):
"""
Enhanced security validation for code.
Returns (is_safe, error_message)
"""
if not isinstance(code, str):
return False, "Code must be a string"
if len(code.strip()) == 0:
return False, "Code cannot be empty"
# Check code size limits
if len(code) > MAX_CODE_SIZE:
return False, f"Code too large (maximum {MAX_CODE_SIZE} bytes allowed)"
# Check for null bytes and other binary content
if '\x00' in code:
return False, "Code contains null bytes"
# Check for dangerous patterns with case-insensitive matching
for pattern in DANGEROUS_PATTERNS:
matches = re.findall(pattern, code, re.IGNORECASE | re.MULTILINE)
if matches:
return False, f"Dangerous operation detected: {pattern} (matched: {matches[0] if matches else 'unknown'})"
# Check for excessive nesting (possible DoS)
nesting_level = 0
max_nesting = 20
for char in code:
if char in '([{':
nesting_level += 1
if nesting_level > max_nesting:
return False, f"Excessive nesting detected (max {max_nesting} levels)"
elif char in ')]}':
nesting_level = max(0, nesting_level - 1)
# Parse AST with enhanced validation
try:
tree = ast.parse(code)
# Check for dangerous AST nodes
for node in ast.walk(tree):
# Import validation
if isinstance(node, ast.Import):
for alias in node.names:
module_name = alias.name.split('.')[0]
if module_name not in ALLOWED_IMPORTS:
return False, f"Import not allowed: {module_name}"
elif isinstance(node, ast.ImportFrom):
if node.module:
module_name = node.module.split('.')[0]
if module_name not in ALLOWED_IMPORTS:
return False, f"Import not allowed: {module_name}"
# Check for attribute access on dangerous modules
elif isinstance(node, ast.Attribute):
if hasattr(node.value, 'id') and node.value.id in ['os', 'sys', 'subprocess']:
return False, f"Dangerous attribute access: {node.value.id}.{node.attr}"
# Check for function calls that might be dangerous
elif isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
if node.func.id in ['exec', 'eval', 'compile', '__import__', 'open', 'input']:
return False, f"Dangerous function call: {node.func.id}"
elif isinstance(node.func, ast.Attribute):
if node.func.attr in ['system', 'popen', 'spawn', 'fork']:
return False, f"Dangerous method call: {node.func.attr}"
# Check for while True loops without breaks (potential infinite loops)
elif isinstance(node, ast.While):
if isinstance(node.test, ast.Constant) and node.test.value is True:
# Check if there's a break statement in the loop
has_break = any(isinstance(n, ast.Break) for n in ast.walk(node))
if not has_break:
return False, "Potentially infinite loop detected (while True without break)"
except SyntaxError as e:
return False, f"Syntax error in code: {str(e)}"
except RecursionError:
return False, "Code too complex (recursion limit exceeded during parsing)"
except Exception as e:
return False, f"Code validation error: {str(e)}"
return True, None
def create_restricted_globals():
"""Create a heavily restricted global namespace for code execution."""
# Very limited set of safe builtins
safe_builtins = {
'abs', 'all', 'any', 'bin', 'bool', 'chr', 'dict', 'enumerate',
'filter', 'float', 'format', 'frozenset', 'hex', 'int', 'isinstance',
'issubclass', 'iter', 'len', 'list', 'map', 'max', 'min', 'next',
'oct', 'ord', 'pow', 'print', 'range', 'repr', 'reversed', 'round',
'set', 'slice', 'sorted', 'str', 'sum', 'tuple', 'type', 'zip'
}
# Create restricted builtins dict with error-raising versions of dangerous functions
restricted_builtins = {}
for name in safe_builtins:
if name in __builtins__ if isinstance(__builtins__, dict) else dir(__builtins__):
if isinstance(__builtins__, dict):
restricted_builtins[name] = __builtins__[name]
else:
restricted_builtins[name] = getattr(__builtins__, name)
# Add error-raising versions of dangerous functions
def raise_security_error(name):
def _error(*args, **kwargs):
raise SecurityViolationError(f"Access to '{name}' is not permitted")
return _error
dangerous_builtins = ['exec', 'eval', 'compile', '__import__', 'open', 'input', 'globals', 'locals', 'vars']
for name in dangerous_builtins:
restricted_builtins[name] = raise_security_error(name)
restricted_globals = {
'__builtins__': restricted_builtins,
'__name__': '__restricted__',
'__doc__': None,
}
# Add allowed modules with error handling
for module in ALLOWED_IMPORTS:
try:
imported_module = __import__(module)
restricted_globals[module] = imported_module
except ImportError:
pass # Module not available, skip
return restricted_globals
def create_secure_temp_environment():
"""Create a secure temporary directory with restricted permissions."""
temp_dir = tempfile.mkdtemp(prefix='secure_code_exec_')
# Set restrictive permissions on the directory
try:
os.chmod(temp_dir, 0o700) # Only owner can read/write/execute
except OSError:
pass # Best effort
return temp_dir
def cleanup_temp_environment(temp_dir):
"""Securely clean up temporary directory and all contents."""
if not temp_dir or not os.path.exists(temp_dir):
return
try:
# Recursively remove all files and subdirectories
for root, dirs, files in os.walk(temp_dir, topdown=False):
for name in files:
file_path = os.path.join(root, name)
try:
os.chmod(file_path, 0o600) # Ensure we can delete
os.unlink(file_path)
except OSError:
pass
for name in dirs:
dir_path = os.path.join(root, name)
try:
os.chmod(dir_path, 0o700) # Ensure we can delete
os.rmdir(dir_path)
except OSError:
pass
os.rmdir(temp_dir)
except Exception as e:
# Log warning but don't fail
print(f"Warning: Could not fully clean up temp directory {temp_dir}: {e}", file=sys.stderr)
def run_code_against_tests(user_code, test_code, max_execution_time=5):
"""
Securely run user code against test code with enhanced safety restrictions.
Args:
user_code: The user's solution code
test_code: The test code to validate the solution
max_execution_time: Maximum execution time in seconds (default: 5)
Returns:
dict: Result containing passed, output, runtime, and error information
"""
# Input validation
if not isinstance(user_code, str) or not isinstance(test_code, str):
return {
'passed': False,
'output': '',
'runtime': 0,
'error': "Both user_code and test_code must be strings"
}
# Validate execution time limit
max_execution_time = min(max(1, int(max_execution_time)), MAX_CPU_TIME)
# Enhanced security validation
user_safe, user_error = validate_code_security(user_code)
if not user_safe:
return {
'passed': False,
'output': '',
'runtime': 0,
'error': f"Security violation in user code: {user_error}"
}
test_safe, test_error = validate_code_security(test_code)
if not test_safe:
return {
'passed': False,
'output': '',
'runtime': 0,
'error': f"Security violation in test code: {test_error}"
}
# Additional test code size validation
if len(test_code) > MAX_TEST_SIZE:
return {
'passed': False,
'output': '',
'runtime': 0,
'error': f"Test code too large (maximum {MAX_TEST_SIZE} bytes allowed)"
}
local_ns = {}
output = ''
start = time.perf_counter()
error = None
passed = False
temp_dir = None
try:
# Check if unittest is used in test_code
if 'unittest' in test_code:
# Create secure temp environment
temp_dir = create_secure_temp_environment()
temp_file = os.path.join(temp_dir, 'test_code.py')
try:
combined_code = f"{user_code}\n\n{test_code}"
# Write to temp file with restricted permissions
with open(temp_file, 'w', encoding='utf-8') as f:
f.write(combined_code)
os.chmod(temp_file, 0o600) # Read/write for owner only
# Prepare secure environment variables
secure_env = {
'PYTHONPATH': '',
'PYTHONDONTWRITEBYTECODE': '1',
'PYTHONUNBUFFERED': '1',
'PATH': '/usr/bin:/bin', # Minimal PATH
}
# Add current Python executable path if needed
python_dir = os.path.dirname(sys.executable)
if python_dir not in secure_env['PATH']:
secure_env['PATH'] = f"{python_dir}:{secure_env['PATH']}"
# Run with subprocess and comprehensive security measures
try:
# Create a wrapper script for additional security
wrapper_code = f"""
import sys
import signal
import resource
def timeout_handler(signum, frame):
raise TimeoutError("Execution timed out")
# Set up timeout handler
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm({max_execution_time})
try:
# Set resource limits
{resource_limits.__code__.co_consts}
with resource_limits():
exec(open(r'{temp_file}').read())
except Exception as e:
print(f"Error: {{e}}", file=sys.stderr)
sys.exit(1)
finally:
signal.alarm(0)
"""
wrapper_file = os.path.join(temp_dir, 'wrapper.py')
with open(wrapper_file, 'w', encoding='utf-8') as f:
f.write(wrapper_code)
os.chmod(wrapper_file, 0o600)
# Use the more secure wrapper approach
proc = subprocess.run(
[sys.executable, temp_file], # Direct execution for now
capture_output=True,
text=True,
timeout=max_execution_time + 1, # Add buffer for subprocess overhead
encoding='utf-8',
cwd=temp_dir,
env=secure_env,
# Additional security on Unix systems
preexec_fn=os.setpgrp if hasattr(os, 'setpgrp') else None
)
# Process output
combined_output = ""
if proc.stdout:
combined_output += proc.stdout
if proc.stderr:
if combined_output:
combined_output += "\n" + proc.stderr
else:
combined_output = proc.stderr
# Limit output size
if len(combined_output) > MAX_OUTPUT_SIZE:
combined_output = combined_output[:MAX_OUTPUT_SIZE] + "\n... (output truncated)"
output = combined_output
passed = proc.returncode == 0
if not passed and proc.returncode != 0:
error = f"Tests failed. Return code: {proc.returncode}"
if output.strip():
error += f"\nOutput: {output}"
except subprocess.TimeoutExpired:
passed = False
error = f"Code execution timed out after {max_execution_time} seconds"
output = "Execution timed out"
except Exception as e:
passed = False
error = f"Subprocess execution error: {str(e)}"
finally:
# Secure cleanup
cleanup_temp_environment(temp_dir)
else:
# Direct execution with heavily restricted globals
old_stdout = sys.stdout
captured_output = io.StringIO()
sys.stdout = captured_output
try:
# Create restricted execution environment
restricted_globals = create_restricted_globals()
# Set up timeout for direct execution
def timeout_handler(signum, frame):
raise TimeoutError("Execution timed out")
if hasattr(signal, 'SIGALRM'):
old_handler = signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(max_execution_time)
try:
# Execute user code in restricted environment
exec(user_code, restricted_globals, local_ns)
# Execute test code
exec(test_code, {**restricted_globals, **local_ns}, local_ns)
passed = True
finally:
if hasattr(signal, 'SIGALRM'):
signal.alarm(0) # Cancel alarm
signal.signal(signal.SIGALRM, old_handler) # Restore handler
except TimeoutError:
passed = False
error = f"Code execution timed out after {max_execution_time} seconds"
except SecurityViolationError as e:
passed = False
error = f"Security violation: {str(e)}"
except AssertionError as e:
passed = False
error = f"Assertion failed: {str(e)}"
except MemoryError:
passed = False
error = "Memory limit exceeded"
except RecursionError:
passed = False
error = "Maximum recursion depth exceeded"
except Exception as e:
passed = False
error = f"Runtime error: {str(e)}"
# Don't include full traceback for security
finally:
output = captured_output.getvalue()
sys.stdout = old_stdout
# Limit output size
if len(output) > MAX_OUTPUT_SIZE:
output = output[:MAX_OUTPUT_SIZE] + "\n... (output truncated)"
except Exception as e:
passed = False
error = f"Execution error: {str(e)}"
if temp_dir:
cleanup_temp_environment(temp_dir)
runtime = time.perf_counter() - start
result = {
'passed': passed,
'output': output.strip() if output else '',
'runtime': runtime,
'error': error if not passed else None
}
return result
def safe_code_runner(user_code, test_code):
"""
Enhanced safety wrapper with comprehensive security checks.
"""
# Input validation
if not isinstance(user_code, str) or not isinstance(test_code, str):
return {
'passed': False,
'output': '',
'runtime': 0,
'error': "Both user_code and test_code must be strings"
}
# Enhanced length checks
if len(user_code) > MAX_CODE_SIZE:
return {
'passed': False,
'output': '',
'runtime': 0,
'error': f"User code too large (maximum {MAX_CODE_SIZE} bytes allowed)"
}
if len(test_code) > MAX_TEST_SIZE:
return {
'passed': False,
'output': '',
'runtime': 0,
'error': f"Test code too large (maximum {MAX_TEST_SIZE} bytes allowed)"
}
# Check for empty code
if not user_code.strip():
return {
'passed': False,
'output': '',
'runtime': 0,
'error': "User code cannot be empty"
}
if not test_code.strip():
return {
'passed': False,
'output': '',
'runtime': 0,
'error': "Test code cannot be empty"
}
return run_code_against_tests(user_code, test_code, MAX_CPU_TIME)

160
static/index.css vendored
View File

@@ -1,160 +0,0 @@
:root {
--bg: #f6f8fb;
--card: #fff;
--muted: #6b7280;
--accent: #2563eb;
--shadow: 0 4px 12px rgba(16, 24, 40, 0.06);
--radius: 8px;
--mono: 'JetBrains Mono', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
}
body {
font-family: Inter, sans-serif;
background: var(--bg);
color: #0f172a;
padding: 16px;
display: flex;
justify-content: center;
align-items: center;
}
.wrap {
width: 100%;
max-width: 1100px;
}
header {
margin-bottom: 14px;
}
header h1 {
font-size: 1.6rem;
color: #111827;
}
header p {
color: var(--muted);
font-size: 0.9rem;
}
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.content.single-column {
grid-template-columns: 1fr;
}
.card {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 12px;
}
/* Search/filter controls */
.search-controls {
margin-bottom: 12px;
display: flex;
gap: 8px;
}
.search-input {
flex: 1;
padding: 6px 10px;
border: 1px solid #e5e7eb;
border-radius: 4px;
font-size: 0.9rem;
}
.filter-select {
padding: 6px 8px;
border: 1px solid #e5e7eb;
border-radius: 4px;
font-size: 0.9rem;
background: white;
}
/* Problems list */
.problems-list .problem-item {
padding: 8px;
border-bottom: 1px solid #e5e7eb;
}
.problem-item:last-child {
border-bottom: none;
}
.problem-item a {
text-decoration: none;
color: #0077ff;
font-weight: 600;
}
/* Leaderboard */
.leaderboard-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.leaderboard-controls {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.leaderboard-table th,
.leaderboard-table td {
padding: 6px 8px;
border-bottom: 1px solid #e5e7eb;
text-align: left;
}
.leaderboard-table th {
background: #f9fafb;
font-weight: 600;
color: var(--muted);
}
.leaderboard-table tr:hover {
background: #f3f4f6;
}
/* Sort indicators */
.sortable {
cursor: pointer;
position: relative;
padding-right: 16px;
}
.sortable::after {
content: "↕";
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 0.8em;
opacity: 0.5;
}
.sort-asc::after {
content: "↑";
opacity: 1;
}
.sort-desc::after {
content: "↓";
opacity: 1;
}
/* Toggle button */
.btn {
border: none;
background: transparent;
cursor: pointer;
color: var(--accent);
font-size: 0.85rem;
padding: 4px 6px;
border-radius: 4px;
}
.btn:hover {
background: rgba(37, 99, 235, 0.08);
}
.btn.active {
background: rgba(37, 99, 235, 0.15);
}
@media (max-width: 800px) {
.content { grid-template-columns: 1fr; }
.leaderboard-controls {
flex-direction: column;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,102 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Quick Problem Platform</title>
<!--<link rel="favicon" href="/favicon/favicon.ico">-->
<script src="script.js" async defer></script>
<link rel="stylesheet" href="/static/index.css">
</head>
<body>
<div class="wrap">
<header>
<h1>Quick Problem Platform</h1>
</header>
<div class="content" id="contentContainer">
<!-- Problems -->
<section class="card problems-list">
<div class="search-controls">
<input
type="text"
class="search-input"
id="problemSearch"
placeholder="Search problems..."
/>
</div>
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
<div id="problemsContainer">
{% for folder, description, test_code in problems %}
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}">
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
</div>
{% else %}
<div class="problem-item">No problems yet.</div>
{% endfor %}
</div>
</section>
<!-- Leaderboard -->
<section class="card" id="leaderboardSection">
<div class="leaderboard-head">
<h2 style="font-size:1.1rem;margin:0">Leaderboard</h2>
<button class="btn" id="toggleLeaderboard">Hide</button>
</div>
<div class="leaderboard-controls">
<input
type="text"
class="search-input"
id="userSearch"
placeholder="Filter by user..."
/>
<input
type="text"
class="search-input"
id="problemFilter"
placeholder="Filter by problem..."
/>
<select class="filter-select" id="runtimeFilter">
<option value="">All runtimes</option>
<option value="best">Best runtime</option>
<option value="worst">Worst runtime</option>
</select>
</div>
<div id="leaderboardContainer">
<table class="leaderboard-table">
<thead>
<tr>
<th class="sortable" data-sort="rank">Rank</th>
<th class="sortable" data-sort="user">User</th>
<th class="sortable" data-sort="problem">Problem</th>
<th class="sortable" data-sort="runtime">Runtime (s)</th>
<th class="sortable" data-sort="memory">Memory (KB)</th>
<th>Line</th>
<th class="sortable" data-sort="timestamp">Timestamp</th>
</tr>
</thead>
<tbody id="leaderboardBody">
{% for entry in leaderboard %}
<tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}"
data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}"
data-timestamp="{{ entry[5] }}">
<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 %}
</tbody>
</table>
</div>
</section>
</div>
</div>
</body>
</html>

View File

@@ -1,66 +0,0 @@
<!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 | safe | markdown }}</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>{{ result.error }}</pre>
{% endif %}
</div>
{% endif %}
</body>
</html>

View File

@@ -1,152 +0,0 @@
// Toggle leaderboard visibility
const toggleBtn = document.getElementById('toggleLeaderboard');
const leaderboardSection = document.getElementById('leaderboardSection');
const contentContainer = document.getElementById('contentContainer');
toggleBtn.addEventListener('click', () => {
if (leaderboardSection.style.display === 'none') {
leaderboardSection.style.display = '';
toggleBtn.textContent = 'Hide';
contentContainer.classList.remove('single-column');
} else {
leaderboardSection.style.display = 'none';
toggleBtn.textContent = 'Show';
contentContainer.classList.add('single-column');
}
});
// Problem search functionality
const problemSearch = document.getElementById('problemSearch');
const problemsContainer = document.getElementById('problemsContainer');
const problemItems = problemsContainer.querySelectorAll('.problem-item');
problemSearch.addEventListener('input', () => {
const searchTerm = problemSearch.value.toLowerCase();
problemItems.forEach(item => {
const name = item.dataset.name.toLowerCase();
const desc = item.dataset.desc?.toLowerCase() || '';
if (name.includes(searchTerm) || desc.includes(searchTerm)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
});
// Leaderboard filtering and sorting
const userSearch = document.getElementById('userSearch');
const problemFilter = document.getElementById('problemFilter');
const runtimeFilter = document.getElementById('runtimeFilter');
const leaderboardBody = document.getElementById('leaderboardBody');
const leaderboardRows = Array.from(leaderboardBody.querySelectorAll('tr'));
const sortableHeaders = document.querySelectorAll('.sortable');
// Current sort state
let currentSort = {
column: null,
direction: 'asc'
};
// Filter leaderboard
function filterLeaderboard() {
const userTerm = userSearch.value.toLowerCase();
const problemTerm = problemFilter.value.toLowerCase();
const runtimeType = runtimeFilter.value;
leaderboardRows.forEach(row => {
const user = row.dataset.user.toLowerCase();
const problem = row.dataset.problem.toLowerCase();
const runtime = parseFloat(row.dataset.runtime);
const showUser = user.includes(userTerm);
const showProblem = problem.includes(problemTerm);
let showRuntime = true;
if (runtimeType === 'best') {
// Find if this is the best runtime for this user+problem combo
const userProblemRows = leaderboardRows.filter(r =>
r.dataset.user === row.dataset.user &&
r.dataset.problem === row.dataset.problem
);
const bestRuntime = Math.min(...userProblemRows.map(r => parseFloat(r.dataset.runtime)));
showRuntime = runtime === bestRuntime;
} else if (runtimeType === 'worst') {
// Find if this is the worst runtime for this user+problem combo
const userProblemRows = leaderboardRows.filter(r =>
r.dataset.user === row.dataset.user &&
r.dataset.problem === row.dataset.problem
);
const worstRuntime = Math.max(...userProblemRows.map(r => parseFloat(r.dataset.runtime)));
showRuntime = runtime === worstRuntime;
}
if (showUser && showProblem && showRuntime) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
// Sort leaderboard
function sortLeaderboard(column, direction) {
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
const index = Array.from(document.querySelectorAll('th')).findIndex(th => th.dataset.sort === column);
rows.sort((a, b) => {
let aValue = a.cells[index].textContent;
let bValue = b.cells[index].textContent;
// Special handling for numeric columns
if (column === 'runtime' || column === 'memory' || column === 'rank') {
aValue = parseFloat(aValue) || 0;
bValue = parseFloat(bValue) || 0;
return direction === 'asc' ? aValue - bValue : bValue - aValue;
}
// Special handling for timestamps
if (column === 'timestamp') {
aValue = new Date(aValue).getTime();
bValue = new Date(bValue).getTime();
return direction === 'asc' ? aValue - bValue : bValue - aValue;
}
// Default string comparison
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
if (aValue < bValue) return direction === 'asc' ? -1 : 1;
if (aValue > bValue) return direction === 'asc' ? 1 : -1;
return 0;
});
// Re-append rows in sorted order
rows.forEach(row => leaderboardBody.appendChild(row));
}
// Set up event listeners
userSearch.addEventListener('input', filterLeaderboard);
problemFilter.addEventListener('input', filterLeaderboard);
runtimeFilter.addEventListener('change', filterLeaderboard);
// Set up sorting
sortableHeaders.forEach(header => {
header.addEventListener('click', () => {
const column = header.dataset.sort;
// Reset all sort indicators
sortableHeaders.forEach(h => {
h.classList.remove('sort-asc', 'sort-desc');
});
// Determine new sort direction
if (currentSort.column === column) {
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
} else {
currentSort.column = column;
currentSort.direction = 'asc';
}
// Apply new sort
header.classList.add(`sort-${currentSort.direction}`);
sortLeaderboard(column, currentSort.direction);
});
});

View File

@@ -1,99 +0,0 @@
import sys
import traceback
import time
import io
import tempfile
import subprocess
import os
def run_code_against_tests(user_code, test_code):
local_ns = {}
output = ''
start = time.perf_counter()
error = None
passed = False
temp_file = None
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, encoding='utf-8') as f:
combined_code = f"{user_code}\n\n{test_code}"
f.write(combined_code)
f.flush()
temp_file = f.name
# Run the file as a subprocess
try:
proc = subprocess.run(
[sys.executable, temp_file],
capture_output=True,
text=True,
timeout=10,
encoding='utf-8'
)
output = proc.stdout
if proc.stderr:
output += f"\n{proc.stderr}"
passed = proc.returncode == 0
if not passed:
error = f"Tests failed. Return code: {proc.returncode}\n{output}"
else:
# For successful unittest runs, the stderr contains the test results
if proc.stderr and "OK" in proc.stderr:
output = proc.stderr # Use stderr as the main output for unittest
except subprocess.TimeoutExpired:
passed = False
error = "Code execution timed out after 10 seconds"
output = "Execution timed out"
else:
# Capture stdout
old_stdout = sys.stdout
captured_output = io.StringIO()
sys.stdout = captured_output
try:
# 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 AssertionError as e:
passed = False
error = f"Assertion failed: {str(e)}"
except Exception as e:
passed = False
error = f"Runtime error: {traceback.format_exc()}"
finally:
output = captured_output.getvalue()
sys.stdout = old_stdout
except Exception as e:
passed = False
error = f"Execution error: {traceback.format_exc()}"
finally:
# Clean up temporary file
if temp_file and os.path.exists(temp_file):
try:
os.unlink(temp_file)
except Exception as e:
print(f"Warning: Could not delete temp file {temp_file}: {e}")
runtime = time.perf_counter() - start
result = {
'passed': passed,
'output': output.strip() if output else '',
'runtime': runtime,
'error': error if not passed else None
}
return result