a2nr 2026-01-02 06:18:48 +07:00
commit 65b24b496a
15 changed files with 1795 additions and 0 deletions

22
Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM python:3.11-slim
# Install gcc compiler for C code compilation
RUN apt-get update && \
apt-get install -y gcc build-essential && \
rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port 5000
EXPOSE 5000
# Run the application with Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "1", "app:app"]

252
README.md Normal file
View File

@ -0,0 +1,252 @@
# C Programming Learning Management System
A web-based learning management system for C programming with interactive exercises and code compilation capabilities.
## Features
- View lessons written in Markdown format
- Interactive C code editor with compilation and execution
- Real-time feedback on code compilation and execution
- No database required - content stored as Markdown files
- No authentication required - ready to use out of the box
- Containerized with Podman for easy deployment
## Prerequisites
- Podman installed on your system
- Basic knowledge of Docker/Podman commands
## Getting Started
### Option 1: Using Podman Compose (Recommended)
1. Navigate to the project directory:
```bash
cd lms-c
```
2. Build and start the container:
```bash
podman-compose -f podman-compose.yml up --build
```
3. Access the application at `http://localhost:5000`
### Option 2: Using the Access Script
1. Make the access script executable:
```bash
chmod +x access_container.sh
```
2. Run the access script:
```bash
./access_container.sh
```
This will start the container and open a shell inside it. From there, you can run:
```bash
python app.py
```
### Option 3: Direct Podman Commands
1. Build the image:
```bash
podman build -t lms-c .
```
2. Run the container:
```bash
podman run -p 5000:5000 -v $(pwd)/content:/app/content -v $(pwd)/static:/app/static -v $(pwd)/templates:/app/templates lms-c
```
## Adding New Lessons
To add new lessons:
1. Create a new Markdown file in the `content` directory with a `.md` extension
2. Structure your lesson with:
- Lesson content at the top (using standard Markdown syntax)
- A separator `---EXERCISE---` (optional)
- Exercise content at the bottom (optional)
### Lesson Structure
**With Exercise:**
```markdown
# Lesson Title
Lesson content goes here...
## Section
More content...
---
EXERCISE---
# Exercise Title
Exercise instructions go here...
Try writing your solution in the code editor below!
```
**Without Exercise:**
```markdown
# Lesson Title
Lesson content goes here...
## Section
More content...
```
### Markdown Features Supported
- Headers: `#`, `##`, `###`
- Code blocks: Use triple backticks (```c for C code highlighting)
- Lists: `-` for unordered, `1.` for ordered
- Bold/italic: `**bold**`, `*italic*`
- Links: `[text](url)`
- Tables: Using standard Markdown table syntax
### Exercise Guidelines
- Exercises are optional - lessons can exist without them
- When an exercise is provided, it will appear above the code editor
- If no exercise is provided, a message will indicate that users can still practice with the code editor
- The code editor is always available for practice regardless of whether an exercise is defined
## How to Use
1. Access the application at `http://localhost:5000`
2. Browse available lessons
3. Click on a lesson to view content and exercises
4. Use the code editor to write C code
5. Click "Run Code" to compile and execute your code
6. View the output in the output panel
## Security Considerations
- The application runs C code in a containerized environment
- Timeouts are implemented to prevent infinite loops
- File system access is limited to the application directory
## Project Structure
- `app.py`: Main Flask application
- `content/`: Directory for lesson Markdown files
- `templates/`: HTML templates
- `static/`: CSS, JavaScript, and other static assets
- `Dockerfile`: Container configuration
- `podman-compose.yml`: Podman Compose configuration
- `requirements.txt`: Python dependencies
## Development
To access the running container for development:
```bash
podman exec -it <container_name> /bin/bash
```
## Troubleshooting
- If you get permission errors, make sure your user has access to Podman
- If the application doesn't start, check that port 5000 is available
- If code compilation fails, verify that the gcc compiler is available in the container
## Stopping the Application
To stop the application:
### If running with Podman Compose:
```bash
# Stop the containers
podman-compose -f podman-compose.yml down
# Or if you want to stop and remove containers, networks, and volumes:
podman-compose -f podman-compose.yml down -v
```
### If running with direct Podman command:
```bash
# Find the container name or ID
podman ps
# Stop the container (replace <container_name> with actual name)
podman stop lms-c-container
# Remove the container after stopping (optional but recommended)
podman rm lms-c-container
# Or do both in one command:
podman stop lms-c-container && podman rm lms-c-container
```
### Using the access script:
If you're currently in the container shell opened via `access_container.sh`, you can:
1. Exit the container shell (type `exit`)
2. Then stop the container from outside:
```bash
podman stop lms-c-container
```
### Troubleshooting: Port Already in Use
If you get an error about the port already being in use, it means the container wasn't properly stopped.
First, check what's using the port:
```bash
podman ps
```
If you see the `lms-c-container` still running, stop it:
```bash
podman stop lms-c-container && podman rm lms-c-container
```
Or stop all running containers:
```bash
podman stop $(podman ps -q)
```
Then you can run the application again on the desired port.
## API Endpoints
- `GET /` - Main page with all lessons
- `GET /lesson/<filename>` - View a specific lesson
- `POST /compile` - Compile and run C code (expects JSON with "code" field)
- `GET /static/<path>` - Serve static files
## Example API Usage
To compile and run C code via the API:
```bash
curl -X POST http://localhost:5000/compile \
-H "Content-Type: application/json" \
-d '{"code": "#include <stdio.h>\\n\\nint main() {\\n printf(\\"Hello, World!\\\\n\\");\\n return 0;\\n}"}'
```
Response:
```json
{
"success": true,
"output": "Hello, World!\n",
"error": null
}
```
In case of compilation errors:
```json
{
"success": false,
"output": "error message...",
"error": "Compilation failed"
}
```

25
access_container.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# Script to access the LMS-C container for development
# This script will start the container (if not running) and open a shell
echo "Starting LMS-C container and opening shell..."
# Check if container is already running
if [ "$(podman ps -q -f name=lms-c-container)" ]; then
echo "Container is already running. Opening shell..."
podman exec -it lms-c-container /bin/bash
else
# Check if container exists but is stopped
if [ "$(podman ps -aq -f name=lms-c-container)" ]; then
echo "Starting existing container..."
podman start lms-c-container
sleep 2
podman exec -it lms-c-container /bin/bash
else
echo "Building and starting container..."
podman build -t lms-c . && podman run -d -p 5000:5000 --name lms-c-container lms-c
sleep 5 # Wait for the application to start
podman exec -it lms-c-container /bin/bash
fi
fi

331
app.py Normal file
View File

@ -0,0 +1,331 @@
#!/usr/bin/env python3
"""
C Programming Learning Management System
A web-based application for learning C programming with exercise compilation
"""
import os
import subprocess
import tempfile
import markdown
from flask import Flask, render_template, request, jsonify, send_from_directory
import glob
app = Flask(__name__)
# Configuration
CONTENT_DIR = 'content'
STATIC_DIR = 'static'
TEMPLATES_DIR = 'templates'
def get_lessons():
"""Get all lesson files from the content directory"""
lesson_files = glob.glob(os.path.join(CONTENT_DIR, "*.md"))
lessons = []
for file_path in lesson_files:
filename = os.path.basename(file_path)
# Skip home.md as it's not a lesson
if filename == "home.md":
continue
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Extract title from first line if it starts with #
lines = content.split('\n')
title = "Untitled"
description = "Learn C programming concepts with practical examples."
for i, line in enumerate(lines):
if line.startswith('# '):
title = line[2:].strip()
# Look for a line after the title that might be a description
elif title != "Untitled" and line.strip() != "" and not line.startswith('#') and i < 10:
# Take the first substantial line as description
clean_line = line.strip().replace('#', '').strip()
if len(clean_line) > 10: # Only if it's a meaningful description
description = clean_line
break
lessons.append({
'filename': filename,
'title': title,
'description': description,
'path': file_path
})
return lessons
def get_ordered_lessons():
"""Get lessons in the order specified in home.md if available"""
# Read home content to check for lesson order
home_file_path = os.path.join(CONTENT_DIR, "home.md")
if os.path.exists(home_file_path):
with open(home_file_path, 'r', encoding='utf-8') as f:
home_content = f.read()
# Split content to get only the lesson list part
parts = home_content.split('---Available_Lessons---')
if len(parts) > 1:
lesson_list_content = parts[1]
# Find lesson links in the lesson list content
import re
# Look for markdown links that point to lessons
lesson_links = re.findall(r'\[([^\]]+)\]\(lesson/([^\)]+)\)', lesson_list_content)
if lesson_links:
# Create ordered list based on home.md
all_lessons = get_lessons()
ordered_lessons = []
for link_text, filename in lesson_links:
for lesson in all_lessons:
if lesson['filename'] == filename:
# Update title to use the link text from home.md if needed
lesson_copy = lesson.copy()
lesson_copy['title'] = link_text
ordered_lessons.append(lesson_copy)
break
# Add any remaining lessons not mentioned in home.md
for lesson in all_lessons:
if not any(l['filename'] == lesson['filename'] for l in ordered_lessons):
ordered_lessons.append(lesson)
return ordered_lessons
# If no specific order is defined in home.md, return lessons in default order
return get_lessons()
def render_markdown_content(file_path):
"""Render markdown content to HTML"""
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Check if there's additional lesson info at the beginning
lesson_info = ""
lesson_content = content
initial_code = ""
solution_code = ""
expected_output = ""
# Look for expected output separator - format: [content] ---EXPECTED_OUTPUT--- [output] ---END_EXPECTED_OUTPUT--- [content]
if '---EXPECTED_OUTPUT---' in lesson_content and '---END_EXPECTED_OUTPUT---' in lesson_content:
start_idx = lesson_content.find('---EXPECTED_OUTPUT---')
end_idx = lesson_content.find('---END_EXPECTED_OUTPUT---')
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
# Extract the expected output between the separators
expected_start = start_idx + len('---EXPECTED_OUTPUT---')
expected_output = lesson_content[expected_start:end_idx].strip()
# Remove the expected output section from the lesson content
lesson_content = lesson_content[:start_idx] + lesson_content[end_idx + len('---END_EXPECTED_OUTPUT---'):]
# Look for lesson info separator - format: ---LESSON_INFO--- [info] ---END_LESSON_INFO--- [content]
if '---LESSON_INFO---' in lesson_content and '---END_LESSON_INFO---' in lesson_content:
start_idx = lesson_content.find('---LESSON_INFO---') + len('---LESSON_INFO---')
end_idx = lesson_content.find('---END_LESSON_INFO---')
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
lesson_info = lesson_content[start_idx:end_idx].strip()
lesson_content = lesson_content[end_idx + len('---END_LESSON_INFO---'):].strip()
else:
# If the format is not correct, treat as before
pass # lesson_content remains the same
elif '---LESSON_INFO---' in lesson_content:
# Fallback for old format: content before first separator is info, after is lesson
parts = lesson_content.split('---LESSON_INFO---', 1)
if len(parts) == 2:
lesson_info = parts[0].strip()
lesson_content = parts[1].strip()
# Look for solution code separator - format: [content] ---SOLUTION_CODE--- [code] ---END_SOLUTION_CODE---
if '---SOLUTION_CODE---' in lesson_content and '---END_SOLUTION_CODE---' in lesson_content:
start_idx = lesson_content.find('---SOLUTION_CODE---')
end_idx = lesson_content.find('---END_SOLUTION_CODE---')
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
# Extract the code between the separators
code_start = start_idx + len('---SOLUTION_CODE---')
solution_code = lesson_content[code_start:end_idx].strip()
# Remove the solution code section from the lesson content
lesson_content = lesson_content[:start_idx] + lesson_content[end_idx + len('---END_SOLUTION_CODE---'):]
# Look for initial code separator - format: [content] ---INITIAL_CODE--- [code] ---END_INITIAL_CODE---
if '---INITIAL_CODE---' in lesson_content and '---END_INITIAL_CODE---' in lesson_content:
start_idx = lesson_content.find('---INITIAL_CODE---')
end_idx = lesson_content.find('---END_INITIAL_CODE---')
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
# Extract the code between the separators
code_start = start_idx + len('---INITIAL_CODE---')
initial_code = lesson_content[code_start:end_idx].strip()
# Remove the initial code section from the lesson content
lesson_content = lesson_content[:start_idx] + lesson_content[end_idx + len('---END_INITIAL_CODE---'):]
# Split content into lesson and exercise parts
parts = lesson_content.split('---EXERCISE---')
lesson_content = parts[0] if len(parts) > 0 else lesson_content
exercise_content = parts[1] if len(parts) > 1 else ""
lesson_html = markdown.markdown(lesson_content, extensions=['fenced_code', 'codehilite', 'tables'])
exercise_html = markdown.markdown(exercise_content, extensions=['fenced_code', 'codehilite', 'tables']) if exercise_content else ""
lesson_info_html = markdown.markdown(lesson_info, extensions=['fenced_code', 'codehilite', 'tables']) if lesson_info else ""
return lesson_html, exercise_html, expected_output, lesson_info_html, initial_code, solution_code
@app.route('/')
def index():
"""Main page showing all lessons"""
lessons = get_ordered_lessons()
# Read home content from a home.md file if it exists
home_content = ""
home_file_path = os.path.join(CONTENT_DIR, "home.md")
if os.path.exists(home_file_path):
with open(home_file_path, 'r', encoding='utf-8') as f:
full_content = f.read()
# Split content to get only the main content part (before the lesson list)
parts = full_content.split('---Available_Lessons---')
main_content = parts[0] if len(parts) > 0 else full_content
home_content = markdown.markdown(main_content, extensions=['fenced_code', 'codehilite', 'tables'])
return render_template('index.html', lessons=lessons, home_content=home_content)
@app.route('/lesson/<filename>')
def lesson(filename):
"""Display a specific lesson"""
file_path = os.path.join(CONTENT_DIR, filename)
if not os.path.exists(file_path):
return "Lesson not found", 404
lesson_html, exercise_html, expected_output, lesson_info, initial_code, solution_code = render_markdown_content(file_path)
# If no initial code is provided, use a default template
if not initial_code:
initial_code = """#include <stdio.h>
int main() {
// Write your code here
printf("Hello, World!\\n");
return 0;
}"""
return render_template('lesson.html',
lesson_content=lesson_html,
exercise_content=exercise_html,
expected_output=expected_output,
lesson_info=lesson_info,
initial_code=initial_code,
solution_code=solution_code,
lesson_title=filename.replace('.md', '').replace('_', ' ').title())
@app.route('/compile', methods=['POST'])
def compile_code():
"""Compile and run C code submitted by the user"""
try:
code = None
# Try to get code from JSON data
if request.content_type and 'application/json' in request.content_type:
try:
json_data = request.get_json(force=True)
if json_data and 'code' in json_data:
code = json_data['code']
except Exception as e:
# Log the error for debugging
print(f"JSON parsing error: {e}")
pass # If JSON parsing fails, continue to try form data
# If not found in JSON, try form data
if not code:
code = request.form.get('code', '')
if not code:
return jsonify({
'success': False,
'output': '',
'error': 'No code provided'
})
# Create a temporary file for the C code
with tempfile.NamedTemporaryFile(mode='w', suffix='.c', delete=False) as temp_c:
temp_c.write(code)
temp_c_path = temp_c.name
# Create a temporary file for the executable
temp_exe_path = temp_c_path.replace('.c', '')
# Compile the C code
compile_result = subprocess.run(
['gcc', temp_c_path, '-o', temp_exe_path],
capture_output=True,
text=True,
timeout=10
)
if compile_result.returncode != 0:
# Compilation failed
result = {
'success': False,
'output': compile_result.stdout, # Include any stdout if available
'error': compile_result.stderr # Show GCC error messages
}
else:
# Compilation succeeded, run the program
try:
run_result = subprocess.run(
[temp_exe_path],
capture_output=True,
text=True,
timeout=5
)
result = {
'success': True,
'output': run_result.stdout,
'error': run_result.stderr if run_result.stderr else None
}
except subprocess.TimeoutExpired:
result = {
'success': False,
'output': '',
'error': 'Program execution timed out'
}
# Clean up temporary files
try:
os.remove(temp_c_path)
if os.path.exists(temp_exe_path):
os.remove(temp_exe_path)
except:
pass # Ignore cleanup errors
return jsonify(result)
except Exception as e:
return jsonify({
'success': False,
'output': '',
'error': f'An error occurred: {str(e)}'
})
@app.route('/static/<path:path>')
def send_static(path):
"""Serve static files"""
return send_from_directory('static', path)
@app.route('/assets/<path:path>')
def send_assets(path):
"""Serve asset files (images, etc.)"""
return send_from_directory('assets', path)
@app.context_processor
def inject_functions():
"""Make get_lessons function available in templates"""
return dict(get_lessons=get_lessons)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

33
content/home.md Normal file
View File

@ -0,0 +1,33 @@
# Welcome to C Programming Learning System
This is a comprehensive learning platform designed to help you master the C programming language through interactive lessons and exercises.
## Learning Objectives
| Module | Objective | Skills Acquired |
|--------|-----------|-----------------|
| Introduction to C | Understand basic syntax and structure | Writing first C program |
| Variables & Data Types | Learn about different data types | Proper variable declaration |
| Control Structures | Master conditional statements and loops | Logic implementation |
| Functions | Create and use functions | Code organization |
| Arrays & Pointers | Work with complex data structures | Memory management |
## How to Use This System
1. **Browse Lessons**: Select from the available lessons on the left
2. **Read Content**: Study the lesson materials and examples
3. **Practice Coding**: Use the integrated code editor to write and test C code
4. **Complete Exercises**: Apply your knowledge to solve programming challenges
5. **Get Feedback**: See immediate results of your code execution
## Getting Started
Start with the "Introduction to C" lesson to begin your journey in C programming. Each lesson builds upon the previous one, so it's recommended to follow them in order.
Happy coding!
---Available_Lessons---
1. [Introduction to C Programming](lesson/introduction_to_c.md)
2. [Variables and Data Types in C](lesson/variables_and_data_types.md)

View File

@ -0,0 +1,123 @@
---LESSON_INFO---
**Learning Objectives:**
- Understand basic syntax and structure of C programs
- Learn how to write your first C program
- Familiarize with the compilation process
**Prerequisites:**
- Basic understanding of programming concepts
- Familiarity with command line interface (optional)
---END_LESSON_INFO---
# Introduction to C Programming
C is a general-purpose, procedural computer programming language supporting structured programming, lexical variable scope, and recursion, with a static type system. By design, C provides constructs that map efficiently to typical machine instructions, which has made it one of the most widely used programming languages.
# Your First C Program Exercise
Write a C program that prints your name and your favorite programming language to the console.
**Requirements:**
- Use `printf` to output your name
- Include your favorite programming language in the output
- Make sure to include the standard input/output library
- The output should be in the format: "Hello, I am [Your Name] and my favorite programming language is [Language]"
**Example Output:**
```
Hello, I am John and my favorite programming language is C
```
Try writing your solution in the code editor below!
## History of C
C was originally developed at Bell Labs by Dennis Ritchie between 1972 and 1973. It was created to re-implement the Unix operating system, which was previously coded in assembly language.
## Basic Structure of a C Program
A basic C program consists of:
- Preprocessor directives
- Function definitions
- Variable declarations
- Comments
- Executable statements
Here's a simple example:
```c
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
```
## Key Concepts
1. **Variables**: Storage locations with a name and data type
2. **Data Types**: Define what kind of value a variable can hold
3. **Functions**: Blocks of code that perform specific tasks
4. **Control Structures**: Direct the flow of program execution
---
## Common Data Types in C
| Type | Description | Example |
|------|-------------|---------|
| int | Integer values | `int age = 25;` |
| float | Floating-point numbers | `float price = 19.99;` |
| char | Single character | `char grade = 'A';` |
| double | Double precision float | `double pi = 3.14159;` |
---EXERCISE---
# Your First C Program Exercise
Write a C program that prints your name and your favorite programming language to the console.
**Requirements:**
- Use `printf` to output your name
- Include your favorite programming language in the output
- Make sure to include the standard input/output library
- The output should be in the format: "Hello, I am [Your Name] and my favorite programming language is [Language]"
**Expected Output:**
```
Hello, I am John and my favorite programming language is C
```
Try writing your solution in the code editor below!
---EXPECTED_OUTPUT---
Hello, I am John and my favorite programming language is C
---END_EXPECTED_OUTPUT---
---INITIAL_CODE---
#include <stdio.h>
int main() {
// Write your code here
printf("Hello, World!\\n");
return 0;
}
---END_INITIAL_CODE---
---SOLUTION_CODE---
#include <stdio.h>
int main() {
// Write your code here
printf("Hello, I am John and my favorite programming language is C\n");
return 0;
}
---END_SOLUTION_CODE---
-

View File

@ -0,0 +1,98 @@
# Variables and Data Types in C
In C programming, variables are used to store data that can be manipulated during program execution. Each variable has a specific data type that determines the size and layout of the variable's memory, the range of values that can be stored, and the set of operations that can be applied to the variable.
## Declaring Variables
![variable](/assets/InitilizationofaVariable-660x330.png)
To use a variable in C, you must first declare it. The syntax for declaring a variable is:
```c
data_type variable_name;
```
For example:
```c
int age;
float height;
char initial;
```
You can also initialize variables at the time of declaration:
```c
int age = 25;
float height = 5.8;
char initial = 'J';
```
## Common Data Types
### Integer Types
- `int`: Used for integers (whole numbers)
- `short`: Short integer
- `long`: Long integer
- `long long`: Extended size integer
### Floating-Point Types
- `float`: Single precision floating-point
- `double`: Double precision floating-point
- `long double`: Extended precision floating-point
### Character Types
- `char`: Single character or small integer
### Void Type
- `void`: Represents the absence of type
## Variable Naming Rules
1. Variable names must begin with a letter or underscore
2. Names can contain letters, digits, and underscores
3. Names are case-sensitive
4. Names cannot be keywords (like `int`, `char`, etc.)
5. Names should be descriptive
## Constants
Constants are fixed values that your program cannot alter during execution. They can be:
- Integer constants: `100`, `-50`, `0`
- Floating-point constants: `3.14`, `-2.5`, `0.001`
- Character constants: `'a'`, `'X'`, `'3'`
- String literals: `"Hello, World!"`
---
---EXERCISE---
# Variables and Data Types Exercise
Write a C program that declares variables of different data types and prints their values.
**Requirements:**
1. Declare an integer variable called `quantity` and assign it the value 42
2. Declare a float variable called `price` and assign it the value 19.99
3. Declare a character variable called `grade` and assign it the value 'A'
4. Declare a double variable called `pi` and assign it the value 3.14159
5. Print all variables with appropriate labels
**Expected Output:**
```
Quantity: 42
Price: 19.990000
Grade: A
Pi: 3.141590
```
Remember to include the stdio.h header file and use the correct format specifiers in printf statements (%d for int, %f for float/double, %c for char).
Try writing your solution in the code editor below!
---EXPECTED_OUTPUT---
Quantity: 42
Price: 19.990000
Grade: A
Pi: 3.141590

14
podman-compose.yml Normal file
View File

@ -0,0 +1,14 @@
version: '3.8'
services:
lms-c:
build: .
ports:
- "5000:5000"
volumes:
- ./content:/app/content
- ./static:/app/static
- ./templates:/app/templates
environment:
- FLASK_ENV=development
command: python app.py

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
Flask==2.3.3
markdown==3.5
gunicorn==21.2.0

24
start.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
# Script to start the C Programming Learning Management System
echo "Starting C Programming Learning Management System..."
# Check if container is already running
if [ "$(podman ps -q -f name=lms-c-container)" ]; then
echo "Container is already running. Access the application at http://localhost:5000"
exit 0
fi
# Check if container exists but is stopped
if [ "$(podman ps -aq -f name=lms-c-container)" ]; then
echo "Starting existing container..."
podman start lms-c-container
else
# Build and run the container
echo "Building and starting container..."
podman build -t lms-c . && podman run -d -p 5000:5000 --name lms-c-container lms-c
fi
echo "Application is now running. Access at http://localhost:5000"
echo "To stop the application, run: ./stop.sh"

330
static/style.css Normal file
View File

@ -0,0 +1,330 @@
/* Custom styles for the C Programming Learning System */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f8f9fa;
}
.lesson-card {
transition: transform 0.2s, box-shadow 0.2s;
height: 100%;
}
.lesson-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.lesson-content {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.lesson-content h1, .lesson-content h2, .lesson-content h3 {
color: #2c3e50;
margin-top: 20px;
}
.lesson-content h1 {
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
}
.lesson-content p {
line-height: 1.6;
margin-bottom: 15px;
}
.lesson-content code {
background-color: #f1f1f1;
padding: 2px 6px;
border-radius: 4px;
font-family: 'Courier New', monospace;
color: #d6336c; /* More colorful */
}
.lesson-content pre {
background-color: #f8f9fa;
color: #333;
padding: 15px;
border-radius: 8px;
border: 1px solid #e9ecef;
overflow-x: auto;
margin: 15px 0;
}
/* More colorful code blocks in lesson content */
.lesson-content pre code {
background: none;
padding: 0;
color: #333;
font-family: 'Courier New', monospace;
}
/* Colorful syntax highlighting for lesson content code blocks */
.lesson-content pre {
background-color: #2b2b2b;
color: #f8f8f2;
border: 1px solid #49483e;
padding: 15px;
border-radius: 8px;
overflow-x: auto;
margin: 15px 0;
}
.lesson-content pre code {
background: none;
color: #f8f8f2;
display: block;
overflow-x: auto;
}
/* Use highlight.js theme for lesson content code blocks */
.lesson-content pre code {
background: #2b2b2b;
padding: 15px;
border-radius: 8px;
overflow-x: auto;
}
/* Override default hljs theme to use dark theme for lesson content */
.lesson-content .hljs {
background: #2b2b2b;
color: #f8f8f2;
padding: 15px;
border-radius: 8px;
overflow-x: auto;
}
.lesson-content .hljs-keyword {
color: #f92672; /* Red for keywords */
}
.lesson-content .hljs-function {
color: #a6e22e; /* Green for functions */
}
.lesson-content .hljs-number {
color: #ae81ff; /* Purple for numbers */
}
.lesson-content .hljs-string {
color: #e6db74; /* Yellow for strings */
}
.lesson-content .hljs-comment {
color: #75715e; /* Brown for comments */
}
.lesson-content .hljs-meta {
color: #f8f8f2; /* Default color for meta */
}
.lesson-content .hljs-title {
color: #a6e22e; /* Green for titles/functions */
}
.lesson-content .hljs-type {
color: #66d9ef; /* Light blue for types */
}
.lesson-content .hljs-params {
color: #fd971f; /* Orange for parameters */
}
.lesson-content .hljs-built_in {
color: #a6e22e; /* Green for built-in functions */
}
.lesson-content table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
.lesson-content table,
.lesson-content th,
.lesson-content td {
border: 1px solid #ddd;
}
.lesson-content th {
background-color: #f2f2f2;
padding: 10px;
text-align: left;
}
.lesson-content td {
padding: 10px;
}
.lesson-content img {
max-width: 100%;
height: auto;
margin: 10px 0;
}
.lesson-content figure {
margin: 15px 0;
text-align: center;
}
.lesson-content figcaption {
font-style: italic;
color: #666;
margin-top: 5px;
}
.lesson-content pre code {
background: none;
padding: 0;
}
.exercise-section {
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.exercise-content {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #3498db;
border-radius: 0 4px 4px 0;
}
.editor-section {
margin-top: 20px;
}
#code-editor {
font-family: 'Courier New', monospace;
font-size: 14px;
resize: vertical;
}
#output {
max-height: 300px;
overflow-y: auto;
}
.output-success {
color: #28a745;
}
.output-error {
color: #dc3545;
}
.footer {
margin-top: 50px;
}
.navbar-brand {
font-weight: bold;
}
.card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.list-group-item.active {
background-color: #3498db;
border-color: #3498db;
}
/* Syntax highlighting for code blocks */
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #f0f0f0;
}
/* Dark theme for code editor */
.code-editor-dark {
background-color: #272822 !important;
color: #f8f8f2 !important;
}
.code-editor-dark #line-numbers {
background-color: #3e3d32 !important;
color: #75715e !important;
}
/* Line numbers container styling */
.line-numbers {
background-color: #eef0f3;
color: #6c757d;
border-right: 1px solid #dee2e6;
font-family: monospace;
font-size: 14px;
line-height: 1.4;
padding: 10px 5px;
text-align: right;
overflow: hidden;
user-select: none;
flex-shrink: 0;
min-width: 40px;
}
/* Dark theme for line numbers */
.code-editor-dark + .line-numbers,
.code-editor-dark .line-numbers {
background-color: #3e3d32;
color: #75715e;
border-right: 1px solid #49483e;
}
/* Alternative selector for dark theme */
.code-editor-dark ~ div .line-numbers,
#editor-wrapper.code-editor-dark .line-numbers {
background-color: #3e3d32;
color: #75715e;
border-right: 1px solid #49483e;
}
/* Editor wrapper styling */
.editor-wrapper {
display: flex;
overflow: hidden;
}
.editor-wrapper textarea {
border: none;
outline: none;
resize: none;
font-family: monospace;
font-size: 14px;
line-height: 1.4;
padding: 10px;
margin: 0;
overflow: auto;
flex: 1;
}
/* Dark theme for output panel */
.output-dark {
background-color: #272822 !important;
color: #f8f8f2 !important;
border: 1px solid #49483e !important;
}
.output-dark pre {
color: #f8f8f2 !important;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.container {
padding-left: 15px;
padding-right: 15px;
}
.lesson-content, .exercise-section {
padding: 15px;
}
}

10
stop.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
# Script to stop the C Programming Learning Management System
echo "Stopping C Programming Learning Management System..."
# Stop and remove the container
podman stop lms-c-container && podman rm lms-c-container
echo "Application has been stopped."

66
templates/index.html Normal file
View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>C Programming Learning System</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="{{ url_for('send_static', path='style.css') }}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-code"></i> C Programming Learning System
</a>
</div>
</nav>
<div class="container mt-4">
<div class="row">
<div class="col-md-12">
<h1 class="mb-4">C Programming Learning System</h1>
<!-- Home content rendered from markdown -->
<div class="home-content mb-5">
{{ home_content | safe }}
</div>
<h2 class="mb-4">Available Lessons</h2>
{% if lessons %}
<div class="row">
{% for lesson in lessons %}
<div class="col-md-6 col-lg-4 mb-4">
<div class="card h-100 lesson-card">
<div class="card-body">
<h5 class="card-title">{{ lesson.title }}</h5>
<p class="card-text">{{ lesson.description }}</p>
</div>
<div class="card-footer">
<a href="{{ url_for('lesson', filename=lesson.filename) }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="alert alert-info">
<h4>No lessons available</h4>
<p>Add markdown files to the content directory to create lessons.</p>
</div>
{% endif %}
</div>
</div>
</div>
<footer class="footer mt-5 py-4 bg-light">
<div class="container text-center">
<span class="text-muted">C Programming Learning System &copy; 2025</span>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

464
templates/lesson.html Normal file
View File

@ -0,0 +1,464 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ lesson_title }} - C Programming Learning System</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
<link rel="stylesheet" href="{{ url_for('send_static', path='style.css') }}">
<style>
.hljs {
background: #f8f9fa;
padding: 12px;
border-radius: 4px;
overflow-x: auto;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-code"></i> C Programming Learning System
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="/"><i class="fas fa-home"></i> Home</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<div class="row">
<div class="col-md-8">
<div class="lesson-content">
{{ lesson_content | safe }}
</div>
<div class="exercise-section mt-5">
<h3><i class="fas fa-laptop-code"></i> Exercise</h3>
{% if exercise_content %}
<div class="exercise-content">
{{ exercise_content | safe }}
</div>
{% else %}
<div class="exercise-content bg-light p-3 rounded">
<p class="mb-0"><em>No specific exercise for this lesson, but you can practice writing C code below.</em></p>
</div>
{% endif %}
<div class="editor-section mt-4">
<h4><i class="fas fa-code"></i> Code Editor</h4>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center" style="background-color: #4a76a8; color: white;">
<span><i class="fas fa-file-code me-2"></i>C code.c</span>
<div>
<button id="theme-toggle" class="btn btn-sm btn-outline-light me-2">
<i class="fas fa-moon"></i> Dark
</button>
<button id="solution-code" class="btn btn-sm btn-outline-light me-2 d-none">
<i class="fas fa-lightbulb"></i> Show Solution
</button>
<button id="run-code" class="btn btn-sm btn-success me-2">
<i class="fas fa-play"></i> Run
</button>
<button id="reset-code" class="btn btn-sm btn-outline-light">
<i class="fas fa-redo"></i> Reset
</button>
</div>
</div>
<div class="card-body p-0">
<div class="code-editor-container">
<div id="editor-wrapper" class="editor-wrapper" style="position: relative; height: 300px; display: flex;">
<div id="line-numbers" class="line-numbers" style="width: 50px; text-align: right; padding: 10px 5px; overflow: hidden; user-select: none; flex-shrink: 0;">
1
</div>
<div style="flex: 1; position: relative;">
<textarea id="code-editor"
style="height: 100%; width: 100%; padding: 10px; font-family: monospace; resize: none; border: none; outline: none; tab-size: 4;"
placeholder="Type your C code here..."></textarea>
</div>
</div>
</div>
</div>
</div>
<!-- Success message card -->
<div id="success-message" class="alert alert-success d-none mt-3" role="alert">
<i class="fas fa-check-circle me-2"></i>
<strong>Tugas anda berhasil!</strong> Output sesuai dengan yang diharapkan.
</div>
<div id="output" class="mt-3 p-3 border rounded bg-light d-none" style="max-height: 200px; overflow-y: auto;">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0"><i class="fas fa-terminal"></i> Output</h5>
<button id="clear-output" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-eraser"></i> Clear
</button>
</div>
<pre id="output-content" class="mb-0"></pre>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-info-circle"></i> Lesson Information</h5>
</div>
<div class="card-body">
<p><strong>Current Lesson:</strong> {{ lesson_title }}</p>
{% if lesson_info %}
<div class="lesson-info-content">
{{ lesson_info | safe }}
</div>
{% else %}
<p class="text-muted">Complete the exercise to practice what you've learned.</p>
{% endif %}
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<h5><i class="fas fa-list"></i> All Lessons</h5>
</div>
<div class="list-group list-group-flush">
{% for lesson in get_lessons() %}
<a href="{{ url_for('lesson', filename=lesson.filename) }}"
class="list-group-item list-group-item-action {% if lesson.filename == request.view_args.filename %}active{% endif %}">
{{ lesson.title }}
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<footer class="footer mt-5 py-4 bg-light">
<div class="container text-center">
<span class="text-muted">C Programming Learning System &copy; 2025</span>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get initial code from the template
const initialCode = {{ initial_code | tojson }};
const solutionCode = {{ solution_code | tojson }};
const expectedOutput = {{ expected_output | tojson }};
// Get DOM elements
const runButton = document.getElementById('run-code');
const resetButton = document.getElementById('reset-code');
const clearOutputButton = document.getElementById('clear-output');
const outputDiv = document.getElementById('output');
const outputContent = document.getElementById('output-content');
const solutionButton = document.getElementById('solution-code');
const themeToggle = document.getElementById('theme-toggle');
const codeEditor = document.getElementById('code-editor');
// Set initial code
codeEditor.value = initialCode;
// Check for saved theme preference
const savedTheme = localStorage.getItem('editor-theme');
let isDarkTheme = savedTheme === 'dark';
// Get the line numbers container (now part of HTML)
const lineNumbersContainer = document.getElementById('line-numbers');
// Function to apply dark theme
function applyDarkTheme() {
codeEditor.classList.add('code-editor-dark');
outputDiv.classList.add('output-dark');
// Update line numbers for dark theme
lineNumbersContainer.style.backgroundColor = '#3e3d32';
lineNumbersContainer.style.color = '#75715e';
lineNumbersContainer.style.borderColor = '#49483e';
codeEditor.style.backgroundColor = '#272822';
codeEditor.style.color = '#f8f8f2';
}
// Function to apply light theme
function applyLightTheme() {
codeEditor.classList.remove('code-editor-dark');
outputDiv.classList.remove('output-dark');
// Update line numbers for light theme
lineNumbersContainer.style.backgroundColor = '#eef0f3';
lineNumbersContainer.style.color = '#6c757d';
lineNumbersContainer.style.borderColor = '#dee2e6';
codeEditor.style.backgroundColor = 'white';
codeEditor.style.color = 'black';
}
// Apply theme based on preference
if (isDarkTheme) {
applyDarkTheme();
themeToggle.innerHTML = '<i class="fas fa-sun"></i> Light';
} else {
applyLightTheme();
themeToggle.innerHTML = '<i class="fas fa-moon"></i> Dark';
}
// Function to update line numbers
function updateLineNumbers() {
const lines = codeEditor.value.split('\n');
const lineCount = lines.length || 1; // Ensure at least 1 line
let lineNumbersHTML = '';
for (let i = 1; i <= lineCount; i++) {
lineNumbersHTML += i + '<br>';
}
lineNumbersContainer.innerHTML = lineNumbersHTML;
}
// Initialize line numbers
updateLineNumbers();
// Update line numbers when content changes
codeEditor.addEventListener('input', updateLineNumbers);
codeEditor.addEventListener('scroll', function() {
lineNumbersContainer.scrollTop = codeEditor.scrollTop;
});
// Update line numbers on other events that might change content
codeEditor.addEventListener('keyup', updateLineNumbers);
codeEditor.addEventListener('paste', function() {
setTimeout(updateLineNumbers, 0);
});
// Prevent copy-paste in the code editor with multiple methods
codeEditor.addEventListener('paste', function(e) {
e.preventDefault();
e.stopPropagation();
alert('Copy-paste is not allowed in the code editor. Please type your code manually.');
return false;
});
// Additional paste prevention using onpaste attribute
codeEditor.onpaste = function(e) {
e.preventDefault();
e.stopPropagation();
alert('Copy-paste is not allowed in the code editor. Please type your code manually.');
return false;
};
codeEditor.addEventListener('copy', function(e) {
e.preventDefault();
e.stopPropagation();
alert('Copy is not allowed in the code editor. Please type your code manually.');
});
codeEditor.addEventListener('cut', function(e) {
e.preventDefault();
e.stopPropagation();
alert('Cut is not allowed in the code editor. Please type your code manually.');
});
// Prevent right-click context menu
codeEditor.addEventListener('contextmenu', function(e) {
e.preventDefault();
e.stopPropagation();
alert('Copy-paste is not allowed in the code editor. Please type your code manually.');
});
// Add keyboard shortcut for running code (Ctrl+Enter)
codeEditor.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
runButton.click();
}
// Handle Tab key to insert 4 spaces instead of changing focus
if (e.key === 'Tab') {
e.preventDefault();
const start = this.selectionStart;
const end = this.selectionEnd;
// Insert 4 spaces at cursor position
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
// Move cursor to after the inserted spaces
this.selectionStart = this.selectionEnd = start + 4;
// Update line numbers after tab
setTimeout(updateLineNumbers, 1);
}
});
// Run code functionality
runButton.addEventListener('click', function() {
const code = codeEditor.value;
if (!code.trim()) {
alert('Please enter some C code to run.');
return;
}
// Show loading state
runButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Running...';
runButton.disabled = true;
// Create form data to send to the server
const formData = new FormData();
formData.append('code', code);
fetch('/compile', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
outputDiv.classList.remove('d-none');
if (data.success) {
outputContent.textContent = data.output || 'Program executed successfully with no output.';
outputContent.className = 'output-success';
// Check if output matches expected output (if available)
if (expectedOutput && expectedOutput !== "None" && expectedOutput !== "") {
const actualOutput = data.output || '';
if (actualOutput.trim() === expectedOutput.trim()) {
// Show success message card if element exists
const successElement = document.getElementById('success-message');
if (successElement) {
// Remove d-none to show the element
successElement.classList.remove('d-none');
// Hide the success message after 10 seconds
setTimeout(function() {
successElement.classList.add('d-none');
}, 10000);
}
// Show solution button if solution code exists
if (solutionButton && solutionCode && solutionCode !== "None" && solutionCode !== "") {
solutionButton.classList.remove('d-none');
}
} else {
// Hide success message if output doesn't match
const successElement = document.getElementById('success-message');
if (successElement) {
successElement.classList.add('d-none');
}
// Hide solution button if output doesn't match
if (solutionButton) {
solutionButton.classList.add('d-none');
}
}
} else {
// Hide success message if no expected output
const successElement = document.getElementById('success-message');
if (successElement) {
successElement.classList.add('d-none');
}
// Hide solution button if no expected output
if (solutionButton) {
solutionButton.classList.add('d-none');
}
}
} else {
outputContent.textContent = data.error || data.output || 'An error occurred.';
outputContent.className = 'output-error';
// Hide success message if there's an error
const successElement = document.getElementById('success-message');
if (successElement) {
successElement.classList.add('d-none');
}
}
})
.catch(error => {
outputDiv.classList.remove('d-none');
outputContent.textContent = 'An error occurred while running the code: ' + error.message;
outputContent.className = 'output-error';
// Hide success message if there's an error
const successElement = document.getElementById('success-message');
if (successElement) {
successElement.classList.add('d-none');
}
})
.finally(() => {
// Reset button state
runButton.innerHTML = '<i class="fas fa-play"></i> Run';
runButton.disabled = false;
});
});
// Reset code functionality
resetButton.addEventListener('click', function() {
codeEditor.value = initialCode;
updateLineNumbers();
});
// Clear output functionality
clearOutputButton.addEventListener('click', function() {
outputDiv.classList.add('d-none');
});
// Add solution button functionality
if (solutionButton && solutionCode && solutionCode !== "None" && solutionCode !== "") {
solutionButton.addEventListener('click', function() {
codeEditor.value = solutionCode;
updateLineNumbers();
});
}
// Function to apply dark theme
function applyDarkTheme() {
codeEditor.classList.add('code-editor-dark');
outputDiv.classList.add('output-dark');
// Update line numbers for dark theme
lineNumbersContainer.style.backgroundColor = '#3e3d32';
lineNumbersContainer.style.color = '#75715e';
codeEditor.style.backgroundColor = '#272822';
codeEditor.style.color = '#f8f8f2';
}
// Function to apply light theme
function applyLightTheme() {
codeEditor.classList.remove('code-editor-dark');
outputDiv.classList.remove('output-dark');
// Update line numbers for light theme
lineNumbersContainer.style.backgroundColor = '#eef0f3';
lineNumbersContainer.style.color = '#6c757d';
codeEditor.style.backgroundColor = 'white';
codeEditor.style.color = 'black';
}
// Theme toggle functionality
themeToggle.addEventListener('click', function() {
isDarkTheme = !isDarkTheme;
if (isDarkTheme) {
applyDarkTheme();
themeToggle.innerHTML = '<i class="fas fa-sun"></i> Light';
localStorage.setItem('editor-theme', 'dark');
} else {
applyLightTheme();
themeToggle.innerHTML = '<i class="fas fa-moon"></i> Dark';
localStorage.setItem('editor-theme', 'light');
}
});
});
</script>
</body>
</html>