first commit

This commit is contained in:
Digital Studium 2025-05-24 08:09:44 +00:00
commit 513f24e655
7 changed files with 366 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.venv/
__pycache__/

8
deploy.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
chown www-data:www-data -R src
cp diff.service /lib/systemd/system/
systemctl enable --now diff
sudo systemctl daemon-reload
sudo systemctl restart diff-diff

15
diff.conf Normal file
View File

@ -0,0 +1,15 @@
server {
listen 80;
server_name diff.digitalstudium.com;
client_max_body_size 0;
location / {
include uwsgi_params;
uwsgi_pass unix:///tmp/diff.sock;
proxy_buffering off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
}

25
diff.service Normal file
View File

@ -0,0 +1,25 @@
[Unit]
Description=Diff web application
After=network.target
[Service]
WorkingDirectory=/srv/diff/src
User=www-data
Group=www-data
# Virtual environment setup
ExecStartPre=/bin/bash -c '[ -d ".venv" ] || python3 -m venv .venv'
ExecStartPre=/bin/bash -c 'source .venv/bin/activate && pip install -r requirements.txt'
# uWSGI execution
ExecStart=/srv/diff/src/.venv/bin/uwsgi --ini uwsgi.ini
Restart=always
Environment="PYTHONUNBUFFERED=1"
StandardOutput=journal
StandardError=journal
SyslogIdentifier=diff
[Install]
WantedBy=multi-user.target

289
src/main.py Normal file
View File

@ -0,0 +1,289 @@
from flask import Flask, request, render_template_string
import difflib
import argparse
app = Flask(__name__)
HTML_TEMPLATE = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Enhanced Diff Tool</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* Modern color scheme */
:root {
--primary: #3b82f6;
--success: #10b981;
--danger: #ef4444;
--background: #ffffff;
--text: #1e293b;
}
[data-theme="dark"] {
--primary: #60a5fa;
--success: #34d399;
--danger: #f87171;
--background: #1e293b;
--text: #f8fafc;
}
body {
font-family: 'Inter', system-ui, sans-serif;
background: var(--background);
color: var(--text);
transition: all 0.2s;
}
.container {
max-width: 1400px;
margin: 2rem auto;
padding: 0 1rem;
}
.diff-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.diff-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.theme-toggle {
cursor: pointer;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
background: var(--primary);
color: white;
}
textarea {
width: 100%;
height: 300px;
padding: 1rem;
border: 2px solid #cbd5e1;
border-radius: 0.5rem;
background: var(--background);
color: var(--text);
font-family: 'JetBrains Mono', monospace;
}
.diff-output {
margin-top: 2rem;
border-radius: 0.5rem;
overflow: auto;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.diff-table {
width: 100%;
border-collapse: collapse;
}
.diff-table td {
padding: 0.5rem 1rem;
vertical-align: top;
font-family: 'JetBrains Mono', monospace;
font-size: 0.875rem;
}
.diff-line-add {
background: #dcfce7;
color: #166534;
}
.diff-line-del {
background: #fee2e2;
color: #991b1b;
}
.diff-line-chg {
background: #fef9c3;
color: #854d0e;
}
.line-number {
color: #64748b;
user-select: none;
}
.word-diff {
background: #ffedd5;
border-radius: 0.25rem;
padding: 0.1rem 0.25rem;
}
</style>
</head>
<body>
<div class="container">
<div class="diff-header">
<h1>Diff Tool</h1>
<div class="theme-toggle" onclick="toggleTheme()">🌓 Toggle Theme</div>
</div>
<form method="post" enctype="multipart/form-data" action="/diff">
<div class="diff-container">
<div>
<h3>Original Content</h3>
<input type="file" name="file1" onchange="previewFile(this, 'text1')">
<textarea id="text1" name="text1" placeholder="Paste or drop text here...">{{ text1 }}</textarea>
</div>
<div>
<h3>Modified Content</h3>
<input type="file" name="file2" onchange="previewFile(this, 'text2')">
<textarea id="text2" name="text2" placeholder="Paste or drop text here...">{{ text2 }}</textarea>
</div>
</div>
<div style="text-align: center;">
<button type="submit">Compare Files</button>
{% if diff_html %}
<button type="button" onclick="downloadDiff()">📥 Download Diff</button>
{% endif %}
</div>
</form>
{% if message %}
<div class="message">{{ message }}</div>
{% endif %}
{% if diff_html %}
<div class="diff-output">
{{ diff_html|safe }}
</div>
{% endif %}
</div>
<script>
// Theme management
function toggleTheme() {
document.body.setAttribute('data-theme',
document.body.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
);
}
// File preview in textareas
async function previewFile(input, textareaId) {
const textarea = document.getElementById(textareaId);
if (input.files && input.files[0]) {
const text = await input.files[0].text();
textarea.value = text;
}
}
// Drag and drop handling
document.querySelectorAll('textarea').forEach(textarea => {
textarea.addEventListener('dragover', e => {
e.preventDefault();
textarea.style.backgroundColor = '#f1f5f9';
});
textarea.addEventListener('dragleave', e => {
textarea.style.backgroundColor = '';
});
textarea.addEventListener('drop', async e => {
e.preventDefault();
textarea.style.backgroundColor = '';
const file = e.dataTransfer.files[0];
if (file) {
textarea.value = await file.text();
}
});
});
// Download diff
function downloadDiff() {
const diffContent = document.querySelector('.diff-output').innerText;
const blob = new Blob([diffContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'diff-result.txt';
a.click();
}
</script>
</body>
</html>
'''
@app.route('/', methods=['GET'])
def index():
return render_template_string(HTML_TEMPLATE,
text1=request.args.get('text1', ''),
text2=request.args.get('text2', ''))
@app.route('/diff', methods=['POST'])
def diff_files():
# Handle file uploads and text inputs
file1 = request.files.get('file1')
file2 = request.files.get('file2')
text1 = request.form.get('text1', '').strip()
text2 = request.form.get('text2', '').strip()
# Validate input
if (not file1 and not text1) or (not file2 and not text2):
return render_template_string(HTML_TEMPLATE,
message="Please provide content for both sides to compare.",
text1=text1,
text2=text2)
try:
# Process first content
if file1 and file1.filename:
content1 = file1.read().decode('utf-8').splitlines()
filename1 = file1.filename
else:
content1 = text1.splitlines()
filename1 = "Original Content"
# Process second content
if file2 and file2.filename:
content2 = file2.read().decode('utf-8').splitlines()
filename2 = file2.filename
else:
content2 = text2.splitlines()
filename2 = "Modified Content"
except UnicodeDecodeError:
return render_template_string(HTML_TEMPLATE,
message="Error: Only UTF-8 encoded text files are supported.",
text1=text1,
text2=text2)
# Generate diff
differ = difflib.HtmlDiff(
tabsize=4,
wrapcolumn=80,
linejunk=None,
charjunk=difflib.IS_CHARACTER_JUNK
)
diff_html = differ.make_file(
content1,
content2,
fromdesc=filename1,
todesc=filename2,
context=True,
numlines=3
)
return render_template_string(HTML_TEMPLATE,
text1=text1,
text2=text2,
diff_html=diff_html)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Run SSL Certificate Decoder")
parser.add_argument('--port', type=int, default=5000, help='Port to run the service on')
parser.add_argument('--host', type=str, default='127.0.0.1', help='Host to run the service on')
args = parser.parse_args()
app.run(debug=True, port=args.port, host=args.host)

15
src/requirements.txt Normal file
View File

@ -0,0 +1,15 @@
astroid==3.3.10
blinker==1.9.0
click==8.2.0
dill==0.4.0
Flask==3.1.1
isort==6.0.1
itsdangerous==2.2.0
Jinja2==3.1.6
MarkupSafe==3.0.2
mccabe==0.7.0
platformdirs==4.3.8
pylint==3.3.7
tomlkit==0.13.2
uWSGI==2.0.29
Werkzeug==3.1.3

12
src/uwsgi.ini Normal file
View File

@ -0,0 +1,12 @@
[uwsgi]
socket = /tmp/diff.sock
chmod-socket = 660
uid = www-data
gid = www-data
module = main:app
master = true
processes = 1
threads = 2
vacuum = true
die-on-term = true