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