Add diff tool
This commit is contained in:
		
							parent
							
								
									97abc99526
								
							
						
					
					
						commit
						c97f7ef2f3
					
				| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
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
 | 
			
		||||
Werkzeug==3.1.3
 | 
			
		||||
		Loading…
	
		Reference in New Issue