Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Python Code Linter | PEP 8 & Flake8 Compliance</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.code-editor { | |
min-height: 300px; | |
font-family: 'Courier New', monospace; | |
font-size: 14px; | |
line-height: 1.5; | |
tab-size: 4; | |
} | |
.report-item { | |
transition: all 0.2s ease; | |
} | |
.report-item:hover { | |
transform: translateX(5px); | |
} | |
.tab-active { | |
border-bottom: 3px solid #3b82f6; | |
} | |
.error-badge { | |
position: absolute; | |
top: -8px; | |
right: -8px; | |
font-size: 12px; | |
} | |
@keyframes pulse { | |
0%, 100% { | |
opacity: 1; | |
} | |
50% { | |
opacity: 0.5; | |
} | |
} | |
.animate-pulse { | |
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<!-- Header --> | |
<header class="mb-8 text-center"> | |
<h1 class="text-4xl font-bold text-blue-600 mb-2"> | |
<i class="fas fa-code mr-2"></i>Python Code Linter | |
</h1> | |
<p class="text-gray-600 text-lg"> | |
Improve your Python code to meet Flake8 and PEP 8 standards | |
</p> | |
</header> | |
<!-- Main Content --> | |
<div class="bg-white rounded-xl shadow-lg overflow-hidden"> | |
<!-- Tabs --> | |
<div class="flex border-b"> | |
<button id="paste-tab" class="tab-active px-6 py-4 font-medium text-blue-600 focus:outline-none"> | |
<i class="fas fa-paste mr-2"></i>Paste Code | |
</button> | |
<button id="upload-tab" class="px-6 py-4 font-medium text-gray-500 hover:text-blue-600 focus:outline-none"> | |
<i class="fas fa-upload mr-2"></i>Upload File | |
</button> | |
<div class="ml-auto px-6 py-4"> | |
<span id="error-count" class="hidden bg-red-100 text-red-800 text-xs font-medium px-2.5 py-0.5 rounded-full"> | |
<span id="error-count-value">0</span> issues found | |
</span> | |
</div> | |
</div> | |
<!-- Tab Content --> | |
<div class="p-6"> | |
<!-- Paste Code Tab Content --> | |
<div id="paste-content" class="space-y-6"> | |
<div class="flex space-x-4"> | |
<div class="flex-1 relative"> | |
<label for="python-code" class="block mb-2 text-sm font-medium text-gray-700"> | |
Original Python Code | |
<span class="text-gray-500 text-xs">(Ctrl+Enter to lint)</span> | |
</label> | |
<div class="relative"> | |
<textarea id="python-code" class="code-editor w-full p-4 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Paste your Python code here..."></textarea> | |
<button id="clear-code" class="absolute top-2 right-2 p-2 text-gray-400 hover:text-gray-600"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
</div> | |
<div class="flex-1 relative"> | |
<label for="linted-code" class="block mb-2 text-sm font-medium text-gray-700"> | |
Linted Python Code | |
</label> | |
<div class="relative"> | |
<pre id="linted-code" class="code-editor w-full p-4 border border-gray-300 rounded-lg bg-gray-50 overflow-auto" style="white-space: pre-wrap;"></pre> | |
<button id="copy-code" class="absolute top-2 right-2 p-2 text-gray-400 hover:text-gray-600"> | |
<i class="fas fa-copy"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="flex justify-center"> | |
<button id="lint-button" class="px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"> | |
<i class="fas fa-magic mr-2"></i>Lint My Code | |
</button> | |
</div> | |
</div> | |
<!-- Upload File Tab Content --> | |
<div id="upload-content" class="hidden space-y-6"> | |
<div class="flex items-center justify-center w-full"> | |
<label for="file-upload" class="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100"> | |
<div class="flex flex-col items-center justify-center pt-5 pb-6"> | |
<i class="fas fa-file-upload text-4xl text-gray-400 mb-3"></i> | |
<p class="mb-2 text-sm text-gray-500"> | |
<span class="font-semibold">Click to upload</span> or drag and drop | |
</p> | |
<p class="text-xs text-gray-500">.py files only</p> | |
</div> | |
<input id="file-upload" type="file" class="hidden" accept=".py" /> | |
</label> | |
</div> | |
<div class="hidden" id="upload-preview"> | |
<div class="flex items-center justify-between mb-4"> | |
<div> | |
<span id="uploaded-filename" class="font-medium text-gray-700"></span> | |
<span id="uploaded-filesize" class="text-sm text-gray-500 ml-2"></span> | |
</div> | |
<button id="remove-file" class="text-red-500 hover:text-red-700"> | |
<i class="fas fa-trash mr-1"></i>Remove | |
</button> | |
</div> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
<div> | |
<h3 class="text-sm font-medium text-gray-700 mb-2">Original Code</h3> | |
<pre id="upload-original-code" class="code-editor w-full p-4 border border-gray-300 rounded-lg bg-gray-50 overflow-auto max-h-64"></pre> | |
</div> | |
<div> | |
<h3 class="text-sm font-medium text-gray-700 mb-2">Linted Code</h3> | |
<pre id="upload-linted-code" class="code-editor w-full p-4 border border-gray-300 rounded-lg bg-gray-50 overflow-auto max-h-64"></pre> | |
</div> | |
</div> | |
<div class="mt-4 flex justify-center"> | |
<button id="lint-upload-button" class="px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"> | |
<i class="fas fa-magic mr-2"></i>Lint Uploaded File | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Linting Report --> | |
<div id="report-section" class="hidden mt-8"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4"> | |
<i class="fas fa-clipboard-list mr-2"></i>Linting Report | |
</h2> | |
<div class="flex mb-4"> | |
<div class="flex-1"> | |
<div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800"> | |
<i class="fas fa-check-circle mr-1"></i> | |
<span id="fixed-count">0</span> issues fixed | |
</div> | |
</div> | |
<div class="flex-1 text-right"> | |
<div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800"> | |
<i class="fas fa-exclamation-triangle mr-1"></i> | |
<span id="remaining-count">0</span> issues remaining | |
</div> | |
</div> | |
</div> | |
<div class="bg-gray-50 rounded-lg border border-gray-200 overflow-hidden"> | |
<div id="report-items" class="divide-y divide-gray-200 max-h-96 overflow-y-auto"> | |
<!-- Report items will be added here dynamically --> | |
</div> | |
</div> | |
<div class="mt-4 text-sm text-gray-500"> | |
<i class="fas fa-info-circle mr-1"></i> PEP 8 is the official style guide for Python code. Flake8 is a tool that checks your code against PEP 8 and other quality standards. | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Features Section --> | |
<div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-6"> | |
<div class="bg-white p-6 rounded-lg shadow-md"> | |
<div class="text-blue-500 text-2xl mb-3"> | |
<i class="fas fa-broom"></i> | |
</div> | |
<h3 class="font-semibold text-lg mb-2">Code Cleanup</h3> | |
<p class="text-gray-600">Automatically fix indentation, whitespace, line length, and other PEP 8 violations to make your code more readable.</p> | |
</div> | |
<div class="bg-white p-6 rounded-lg shadow-md"> | |
<div class="text-blue-500 text-2xl mb-3"> | |
<i class="fas fa-search"></i> | |
</div> | |
<h3 class="font-semibold text-lg mb-2">Comprehensive Checks</h3> | |
<p class="text-gray-600">Detects and reports on style issues, syntax errors, and potential bugs using Flake8's powerful analysis.</p> | |
</div> | |
<div class="bg-white p-6 rounded-lg shadow-md"> | |
<div class="text-blue-500 text-2xl mb-3"> | |
<i class="fas fa-graduation-cap"></i> | |
</div> | |
<h3 class="font-semibold text-lg mb-2">Learn Best Practices</h3> | |
<p class="text-gray-600">Detailed explanations help you understand and remember Python coding standards for future projects.</p> | |
</div> | |
</div> | |
<!-- Footer --> | |
<footer class="mt-12 text-center text-gray-500 text-sm"> | |
<p>Made with <i class="fas fa-heart text-red-500"></i> for Python developers</p> | |
<p class="mt-1">This tool helps improve code quality but doesn't replace thorough testing and code reviews.</p> | |
</footer> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Tab switching | |
const pasteTab = document.getElementById('paste-tab'); | |
const uploadTab = document.getElementById('upload-tab'); | |
const pasteContent = document.getElementById('paste-content'); | |
const uploadContent = document.getElementById('upload-content'); | |
pasteTab.addEventListener('click', function() { | |
pasteTab.classList.add('tab-active'); | |
pasteTab.classList.remove('text-gray-500'); | |
pasteTab.classList.add('text-blue-600'); | |
uploadTab.classList.remove('tab-active'); | |
uploadTab.classList.add('text-gray-500'); | |
uploadTab.classList.remove('text-blue-600'); | |
pasteContent.classList.remove('hidden'); | |
uploadContent.classList.add('hidden'); | |
}); | |
uploadTab.addEventListener('click', function() { | |
uploadTab.classList.add('tab-active'); | |
uploadTab.classList.remove('text-gray-500'); | |
uploadTab.classList.add('text-blue-600'); | |
pasteTab.classList.remove('tab-active'); | |
pasteTab.classList.add('text-gray-500'); | |
pasteTab.classList.remove('text-blue-600'); | |
uploadContent.classList.remove('hidden'); | |
pasteContent.classList.add('hidden'); | |
}); | |
// Code editor functionality | |
const pythonCode = document.getElementById('python-code'); | |
const lintedCode = document.getElementById('linted-code'); | |
const clearCode = document.getElementById('clear-code'); | |
const copyCode = document.getElementById('copy-code'); | |
const lintButton = document.getElementById('lint-button'); | |
const errorCount = document.getElementById('error-count'); | |
const errorCountValue = document.getElementById('error-count-value'); | |
const reportSection = document.getElementById('report-section'); | |
const reportItems = document.getElementById('report-items'); | |
const fixedCount = document.getElementById('fixed-count'); | |
const remainingCount = document.getElementById('remaining-count'); | |
// Sample linting function (in a real app, this would call a backend service) | |
function lintPythonCode(code) { | |
// This is a mock implementation | |
// In reality, you would send this to a backend that runs flake8 and autopep8 | |
// Mock fixes | |
const mockFixes = [ | |
{ line: 2, column: 5, message: "E302 expected 2 blank lines before function definition", fixed: true }, | |
{ line: 5, column: 80, message: "E501 line too long (82 > 79 characters)", fixed: true }, | |
{ line: 7, column: 1, message: "E303 too many blank lines (3)", fixed: true }, | |
{ line: 9, column: 17, message: "E225 missing whitespace around operator", fixed: true }, | |
{ line: 12, column: 1, message: "W391 blank line at end of file", fixed: true }, | |
{ line: 3, column: 1, message: "E265 block comment should start with '# '", fixed: false }, | |
]; | |
// Mock linted code (just some basic formatting for demo) | |
let linted = code; | |
// Add two blank lines before functions | |
linted = linted.replace(/def (\w+)\(/g, '\n\n$&'); | |
// Fix line length (very simplistic) | |
linted = linted.split('\n').map(line => { | |
if (line.length > 79) { | |
return line.substring(0, 76) + '...'; | |
} | |
return line; | |
}).join('\n'); | |
// Trim trailing whitespace | |
linted = linted.split('\n').map(line => line.trimEnd()).join('\n'); | |
// Ensure exactly one blank line at end of file | |
linted = linted.trimEnd() + '\n'; | |
return { | |
lintedCode: linted, | |
issues: mockFixes, | |
originalCode: code | |
}; | |
} | |
// Generate a report item | |
function createReportItem(issue, index) { | |
const item = document.createElement('div'); | |
item.className = 'report-item p-4 hover:bg-gray-100'; | |
const badgeClass = issue.fixed ? | |
'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'; | |
const icon = issue.fixed ? 'check-circle' : 'exclamation-triangle'; | |
item.innerHTML = ` | |
<div class="flex items-start"> | |
<div class="flex-shrink-0 pt-0.5"> | |
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${badgeClass}"> | |
<i class="fas fa-${icon} mr-1"></i> | |
${issue.fixed ? 'Fixed' : 'Warning'} | |
</span> | |
</div> | |
<div class="ml-3"> | |
<div class="text-sm font-medium text-gray-900"> | |
Line ${issue.line}, Column ${issue.column} | |
</div> | |
<div class="text-sm text-gray-500"> | |
${issue.message} | |
</div> | |
</div> | |
</div> | |
`; | |
return item; | |
} | |
// Clear code editor | |
clearCode.addEventListener('click', function() { | |
pythonCode.value = ''; | |
lintedCode.textContent = ''; | |
reportSection.classList.add('hidden'); | |
errorCount.classList.add('hidden'); | |
}); | |
// Copy linted code | |
copyCode.addEventListener('click', function() { | |
if (lintedCode.textContent.trim()) { | |
navigator.clipboard.writeText(lintedCode.textContent); | |
// Show feedback | |
const originalIcon = copyCode.querySelector('i'); | |
originalIcon.classList.remove('fa-copy'); | |
originalIcon.classList.add('fa-check'); | |
setTimeout(() => { | |
originalIcon.classList.remove('fa-check'); | |
originalIcon.classList.add('fa-copy'); | |
}, 2000); | |
} | |
}); | |
// Lint code | |
function performLint() { | |
const code = pythonCode.value.trim(); | |
if (!code) return; | |
// Show loading state | |
lintButton.innerHTML = '<i class="fas fa-spinner animate-spin mr-2"></i> Linting...'; | |
lintButton.disabled = true; | |
// Simulate API call delay | |
setTimeout(() => { | |
const result = lintPythonCode(code); | |
// Update UI with results | |
lintedCode.textContent = result.lintedCode; | |
// Show error count | |
const totalIssues = result.issues.length; | |
const fixedIssues = result.issues.filter(i => i.fixed).length; | |
if (totalIssues > 0) { | |
errorCountValue.textContent = totalIssues; | |
errorCount.classList.remove('hidden'); | |
} else { | |
errorCount.classList.add('hidden'); | |
} | |
// Generate report | |
reportItems.innerHTML = ''; | |
result.issues.forEach((issue, index) => { | |
reportItems.appendChild(createReportItem(issue, index)); | |
}); | |
// Update counts | |
fixedCount.textContent = fixedIssues; | |
remainingCount.textContent = totalIssues - fixedIssues; | |
// Show report | |
reportSection.classList.remove('hidden'); | |
// Reset button | |
lintButton.innerHTML = '<i class="fas fa-magic mr-2"></i>Lint My Code'; | |
lintButton.disabled = false; | |
}, 1000); | |
} | |
lintButton.addEventListener('click', performLint); | |
// Add Ctrl+Enter shortcut to lint code | |
pythonCode.addEventListener('keydown', function(e) { | |
if (e.ctrlKey && e.key === 'Enter') { | |
performLint(); | |
} | |
}); | |
// File upload functionality | |
const fileUpload = document.getElementById('file-upload'); | |
const uploadPreview = document.getElementById('upload-preview'); | |
const uploadedFilename = document.getElementById('uploaded-filename'); | |
const uploadedFilesize = document.getElementById('uploaded-filesize'); | |
const uploadOriginalCode = document.getElementById('upload-original-code'); | |
const uploadLintedCode = document.getElementById('upload-linted-code'); | |
const removeFile = document.getElementById('remove-file'); | |
const lintUploadButton = document.getElementById('lint-upload-button'); | |
fileUpload.addEventListener('change', function(e) { | |
const file = e.target.files[0]; | |
if (!file) return; | |
if (!file.name.endsWith('.py')) { | |
alert('Please upload a Python (.py) file'); | |
return; | |
} | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
const content = e.target.result; | |
// Update UI | |
uploadedFilename.textContent = file.name; | |
uploadedFilesize.textContent = formatFileSize(file.size); | |
uploadOriginalCode.textContent = content; | |
uploadLintedCode.textContent = ''; | |
// Show preview | |
uploadPreview.classList.remove('hidden'); | |
}; | |
reader.readAsText(file); | |
}); | |
removeFile.addEventListener('click', function() { | |
fileUpload.value = ''; | |
uploadPreview.classList.add('hidden'); | |
}); | |
lintUploadButton.addEventListener('click', function() { | |
const code = uploadOriginalCode.textContent.trim(); | |
if (!code) return; | |
// Show loading state | |
lintUploadButton.innerHTML = '<i class="fas fa-spinner animate-spin mr-2"></i> Linting...'; | |
lintUploadButton.disabled = true; | |
// Simulate API call delay | |
setTimeout(() => { | |
const result = lintPythonCode(code); | |
// Update UI with results | |
uploadLintedCode.textContent = result.lintedCode; | |
// Generate report | |
reportItems.innerHTML = ''; | |
result.issues.forEach((issue, index) => { | |
reportItems.appendChild(createReportItem(issue, index)); | |
}); | |
// Update counts | |
const totalIssues = result.issues.length; | |
const fixedIssues = result.issues.filter(i => i.fixed).length; | |
fixedCount.textContent = fixedIssues; | |
remainingCount.textContent = totalIssues - fixedIssues; | |
// Show report | |
reportSection.classList.remove('hidden'); | |
// Reset button | |
lintUploadButton.innerHTML = '<i class="fas fa-magic mr-2"></i>Lint Uploaded File'; | |
lintUploadButton.disabled = false; | |
}, 1000); | |
}); | |
// Helper function to format file size | |
function formatFileSize(bytes) { | |
if (bytes === 0) return '0 Bytes'; | |
const k = 1024; | |
const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
} | |
// Sample code for first-time users | |
if (!pythonCode.value.trim()) { | |
pythonCode.value = `# This is a sample Python code with some style issues | |
def calculate_sum(a,b): | |
result=a+b | |
return result | |
def print_message(msg): | |
#This function prints a message | |
print("Message:",msg) | |
print_message("Hello World!") | |
total=calculate_sum(5,7) | |
print("The total is:",total) | |
`; | |
} | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=S-Dreamer/python-code-linter" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |