From 5fe140c4f900ec2dba11fb01a8c3f6581558874b Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Tue, 12 Aug 2025 12:47:35 +0200 Subject: [PATCH] fuck you all --- app.py | 71 +++++++++---- problem_scanner.py | 118 +++++++++++++++++++++ problems.json | 12 --- problems/reversedstring/description.md | 29 +++++ problems/reversedstring/manifest.json | 6 ++ problems/reversedstring/test.py | 19 ++++ problems/solution_reversed_string.py | 14 --- problems/sortlist/description.md | 1 + problems/sortlist/manifets.json | 6 ++ problems/{sortlist.py => sortlist/test.py} | 5 + templates/index.html | 6 +- templates/problem.html | 10 +- 12 files changed, 239 insertions(+), 58 deletions(-) create mode 100644 problem_scanner.py delete mode 100644 problems.json create mode 100644 problems/reversedstring/description.md create mode 100644 problems/reversedstring/manifest.json create mode 100644 problems/reversedstring/test.py delete mode 100644 problems/solution_reversed_string.py create mode 100644 problems/sortlist/description.md create mode 100644 problems/sortlist/manifets.json rename problems/{sortlist.py => sortlist/test.py} (64%) diff --git a/app.py b/app.py index 728e727..03d206f 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,17 @@ +from markupsafe import Markup from flask import Flask, render_template, request, redirect, url_for +import markdown as md + from models import db, Problem, Solution from utils import run_code_against_tests from leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard import os -from problem_loader import load_problems_from_json, schedule_problem_reload +## from problem_loader import load_problems_from_json, schedule_problem_reload +from problem_scanner import start_problem_scanner +import sqlite3 +from pathlib import Path app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' @@ -17,18 +23,23 @@ db.init_app(app) def setup(): db.create_all() create_leaderboard_table() # Ensure leaderboard table exists - # Load problems from JSON at startup - json_path = os.path.join(os.path.dirname(__file__), 'problems.json') - load_problems_from_json(json_path) - # Schedule reload every 10 hours - schedule_problem_reload(app, json_path, interval_hours=10) + # Problems are now loaded from manifests by the background scanner. No need to load problems.json. + +# Start the background thread to scan problems +start_problem_scanner() @app.route('/') def index(): - problems = Problem.query.all() + db_path = Path(__file__).parent / 'problems.sqlite3' + conn = sqlite3.connect(db_path) + c = conn.cursor() + c.execute('SELECT folder, description, test_code FROM problems') + problems = c.fetchall() + conn.close() + # Get leaderboard entries leaderboard = get_leaderboard() - # Map problem_id to title for leaderboard display - problem_titles = {p.id: p.title for p in problems} + # Map folder to title for display + problem_titles = {folder: folder.replace('_', ' ').title() for folder, _, _ in problems} return render_template('index.html', problems=problems, leaderboard=leaderboard, problem_titles=problem_titles) @app.route('/problem/new', methods=['GET', 'POST']) @@ -43,16 +54,29 @@ def new_problem(): return redirect(url_for('index')) return render_template('new_problem.html') -@app.route('/problem/', methods=['GET', 'POST']) -def view_problem(problem_id): - problem = Problem.query.get_or_404(problem_id) +@app.route('/problem/', methods=['GET', 'POST']) +def view_problem(folder): + db_path = Path(__file__).parent / 'problems.sqlite3' + conn = sqlite3.connect(db_path) + c = conn.cursor() + c.execute('SELECT folder, description, test_code FROM problems WHERE folder = ?', (folder,)) + row = c.fetchone() + conn.close() + if not row: + return 'Problem not found', 404 + problem = { + 'folder': row[0], + 'description': row[1], + 'test_code': row[2], + 'title': row[0].replace('_', ' ').title() + } result = None if request.method == 'POST': user_code = request.form['user_code'] username = request.form.get('username', '').strip() or 'Anonymous' import tracemalloc tracemalloc.start() - run_result = run_code_against_tests(user_code, problem.test_code) + 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 @@ -61,10 +85,13 @@ def view_problem(problem_id): import ast try: tree = ast.parse(user_code) - # Get the last line number in the user's code - if hasattr(tree, 'body') and tree.body: - last_node = tree.body[-1] - line_number = getattr(last_node, 'lineno', None) + # 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 @@ -77,13 +104,13 @@ def view_problem(problem_id): line_number = tb_lines[-1].lineno except Exception: pass - log_leaderboard(username, problem.id, run_result['runtime'], memory_used, line_number) - solution = Solution(problem_id=problem.id, user_code=user_code, passed=run_result['passed'], output=run_result['output']) - db.session.add(solution) - db.session.commit() + 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) diff --git a/problem_scanner.py b/problem_scanner.py new file mode 100644 index 0000000..e557857 --- /dev/null +++ b/problem_scanner.py @@ -0,0 +1,118 @@ +import os +import time +import json +import sqlite3 +import threading +import random +from pathlib import Path +import sys + +try: + from watchdog.observers import Observer + from watchdog.events import FileSystemEventHandler + WATCHDOG_AVAILABLE = True +except ImportError: + WATCHDOG_AVAILABLE = False + +PROBLEMS_DIR = Path(__file__).parent / 'problems' +DB_PATH = Path(__file__).parent / 'problems.sqlite3' + +class ProblemScannerThread(threading.Thread): + def __init__(self, scan_interval=2): + super().__init__(daemon=True) + self.scan_interval = scan_interval + self.last_state = {} + self.observer = None + + def create_table(self, conn): + c = conn.cursor() + c.execute('PRAGMA journal_mode=WAL;') + c.execute('''CREATE TABLE IF NOT EXISTS problems ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + folder TEXT, + description TEXT, + test_code TEXT + )''') + conn.commit() + + def scan(self): + problems = [] + for folder in PROBLEMS_DIR.iterdir(): + if folder.is_dir(): + # Dynamically find manifest file (manifest.json or manifets.json) + manifest_path = None + for candidate in ["manifest.json", "manifets.json"]: + candidate_path = folder / candidate + if candidate_path.exists(): + manifest_path = candidate_path + break + desc_path = folder / 'description.md' + test_path = folder / 'test.py' + if manifest_path and test_path.exists(): + with open(desc_path, 'r') as f: + description = f.read() + with open(test_path, 'r') as f: + test_code = f.read() + problems.append({ + 'folder': folder.name, + 'description': description, + 'test_code': test_code + }) + return problems + + def update_db(self, problems, retries=5): + for attempt in range(retries): + try: + conn = sqlite3.connect(DB_PATH, timeout=2) + c = conn.cursor() + c.execute('PRAGMA journal_mode=WAL;') + c.execute('DELETE FROM problems') + for p in problems: + c.execute('INSERT INTO problems (folder, description, test_code) VALUES (?, ?, ?)', + (p['folder'], p['description'], p['test_code'])) + conn.commit() + conn.close() + return + except sqlite3.OperationalError as e: + if 'locked' in str(e): + time.sleep(0.2 + random.random() * 0.3) + else: + raise + print('Failed to update problems DB after several retries due to lock.') + + def rescan_and_update(self): + problems = self.scan() + self.update_db(problems) + + def run(self): + # Initial scan and table creation + conn = sqlite3.connect(DB_PATH) + self.create_table(conn) + conn.close() + self.rescan_and_update() + if WATCHDOG_AVAILABLE: + class Handler(FileSystemEventHandler): + def __init__(self, scanner): + self.scanner = scanner + def on_any_event(self, event): + self.scanner.rescan_and_update() + event_handler = Handler(self) + self.observer = Observer() + self.observer.schedule(event_handler, str(PROBLEMS_DIR), recursive=True) + self.observer.start() + try: + while True: + time.sleep(1) + finally: + self.observer.stop() + self.observer.join() + else: + # Fallback: poll every scan_interval seconds + while True: + time.sleep(self.scan_interval) + self.rescan_and_update() + +def start_problem_scanner(): + scanner = ProblemScannerThread() + scanner.start() + return scanner diff --git a/problems.json b/problems.json deleted file mode 100644 index 2d6070f..0000000 --- a/problems.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "title": "Reversed String", - "description": "Reverse a String with a Function (revstring); the function is supposed to take the string as an argument and is supposed to return the reversed string and print it.", - "solution": "problems/solution_reversed_string.py" - }, - { - "title": "Sort List", - "description": "Sort a List with a Function (sortlist); the function is supposed to take the list as an argument and is supposed to return the sorted list and print it.", - "solution": "problems/sortlist.py" - } -] diff --git a/problems/reversedstring/description.md b/problems/reversedstring/description.md new file mode 100644 index 0000000..3d3f91d --- /dev/null +++ b/problems/reversedstring/description.md @@ -0,0 +1,29 @@ +## Reversed String + +Write a function called ```revstring``` that takes a string as input and returns the reversed string. + +### Function Signature: +```python +def revstring(x). + # return your solution +``` + +#### Requirements + - The function should return the input string reversed + - Your function will be tested with various cases, including: + - An empty string + - A single character + - A palindrome ("racecar") + - A string of numbers ("12345") + - Special characters + - A normal string ( "Hello World" ) + +#### Example: +```python +revstring("Hello World") # returns "dlroW olleH" +revstring("") # returns "" +revstring("racecar") # returns "racecar" +revstring("12345") # returns "54321" +revstring("!@# $%") # returns "%$ #@!" +``` +You can copy this into your problems solution \ No newline at end of file diff --git a/problems/reversedstring/manifest.json b/problems/reversedstring/manifest.json new file mode 100644 index 0000000..4cd8985 --- /dev/null +++ b/problems/reversedstring/manifest.json @@ -0,0 +1,6 @@ +{ + "title":"Reversed String", + "description":"Reverse a String using a Function ; Try to write as little code as possible", + "description_md":"problems/reversedstring/description.md", + "test_code":"problems/reversedstring/test.py" +} \ No newline at end of file diff --git a/problems/reversedstring/test.py b/problems/reversedstring/test.py new file mode 100644 index 0000000..413de68 --- /dev/null +++ b/problems/reversedstring/test.py @@ -0,0 +1,19 @@ +import unittest + +# +def revstring(x): + return x[::-1] + +# +class TestSolution(unittest.TestCase): + def test_simple(self): + x = "Hello World" + self.assertEqual(revstring(x), "dlroW olleH") # Test simple string reversal + self.assertEqual(revstring(""), "") # Test empty string + self.assertEqual(revstring("a"), "a") # Test single character + self.assertEqual(revstring("racecar"), "racecar") # Test palindrome + self.assertEqual(revstring("12345"), "54321") # Test numbers as string + self.assertEqual(revstring("!@# $%"), "%$ #@!") # Test special chars and spaces + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/problems/solution_reversed_string.py b/problems/solution_reversed_string.py deleted file mode 100644 index 93f2872..0000000 --- a/problems/solution_reversed_string.py +++ /dev/null @@ -1,14 +0,0 @@ -import unittest - -# -def revstring(x): - return x[::-1] - -# -class TestSolution(unittest.TestCase): - def test_simple(self): - x=""; - self.assertEqual(revstring(x), x[::-1]) - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/problems/sortlist/description.md b/problems/sortlist/description.md new file mode 100644 index 0000000..72acc64 --- /dev/null +++ b/problems/sortlist/description.md @@ -0,0 +1 @@ +this is a easy sorting problem **it is solvable in less than 2 seconds** \ No newline at end of file diff --git a/problems/sortlist/manifets.json b/problems/sortlist/manifets.json new file mode 100644 index 0000000..b10280c --- /dev/null +++ b/problems/sortlist/manifets.json @@ -0,0 +1,6 @@ +{ + "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_md": "problems/sortlist/description.md", + "test_code": "problems/sortlist/test.py" +} \ No newline at end of file diff --git a/problems/sortlist.py b/problems/sortlist/test.py similarity index 64% rename from problems/sortlist.py rename to problems/sortlist/test.py index 6ace00c..3e32f1c 100644 --- a/problems/sortlist.py +++ b/problems/sortlist/test.py @@ -1,5 +1,10 @@ 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. diff --git a/templates/index.html b/templates/index.html index a18b5e7..173f5b5 100644 --- a/templates/index.html +++ b/templates/index.html @@ -61,8 +61,10 @@

Problems

    - {% for problem in problems %} -
  • {{ problem.title }}
  • + {% for folder, description, test_code in problems %} +
  • + {{ folder.replace('_', ' ').title() }} +
  • {% else %}
  • No problems yet.
  • {% endfor %} diff --git a/templates/problem.html b/templates/problem.html index 8d725ff..c30a6dc 100644 --- a/templates/problem.html +++ b/templates/problem.html @@ -12,7 +12,7 @@

    {{ problem.title }}

    -
    {{ problem.description }}
    +
    {{ problem.description | safe | markdown }}

    Submit Your Solution (Python)

    @@ -58,15 +58,9 @@
    {{ result.output }}
    {% if result.error %} Error: -
    {{ result.error }}
    - {% endif %} - {% if result.passed %} -

    Passed!

    - {% else %} -

    Failed!

    +
    {{ result.error }}
    {% endif %}
    {% endif %} -