Files
2024-12-25 15:05:39 -08:00

254 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python requirements.txt Generator</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<style>
.drop-zone {
border: 2px dashed #cbd5e0;
transition: all 0.3s ease;
}
.drop-zone.dragover {
border-color: #4299e1;
background-color: #ebf8ff;
}
.results-box {
max-height: 400px;
overflow-y: auto;
}
</style>
</head>
<body class="bg-gray-50">
<div class="min-h-screen p-6">
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold text-gray-800 mb-8">Python requirements.txt Generator</h1>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h3 class="text-1xl font-bold text-gray-800 mb-2">About this tool</h3>
<p>
This tool is used to help you find out what original python package versions were used if you are missing a requirements.txt or it is
not included in the program's documentation. In most cases, it will get you 99% of the way there, so depreciations and behavior changes
won't bite you as hard. Take these estimations with a grain of salt.
</p>
<br>
<ul>
<li>1. Use an original unmodified file - due to JS limitations (skill issue), it cannot use the creation date and it will improperly resolve
unless there's a git repo in a .zip.
</li>
<li>2. Relative imports may not be resolvable - you will need to check this manually</li>
<li>2a. Some relative imports with common generic names (tools, utils) will incorrectly resolve to a pip package when they are actually
a relative import.
</li>
</ul>
<br>
<p>
It is up to you, the user, to double check your imports. This tool is just to get you close.
</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<div id="dropZone" class="drop-zone rounded-lg p-8 text-center cursor-pointer">
<div class="text-gray-500">
<p class="text-lg mb-2">Drag and drop your Python file or ZIP archive here</p>
<p class="text-sm mb-4">(or click to select)</p>
<p class="text-xs">Maximum file size: 10MB</p>
</div>
</div>
<input type="file" id="fileInput" class="hidden" accept=".py,.zip">
</div>
<div id="resultsContainer" class="hidden bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Analysis Results</h2>
<div id="loadingIndicator" class="hidden">
<div class="flex items-center justify-center py-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
</div>
</div>
<div id="results" class="results-box font-mono text-sm bg-gray-50 rounded p-4 mb-4"></div>
<button id="downloadBtn" class="hidden bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded">
Download requirements.txt
</button> <br><br>
<script async
src="https://js.stripe.com/v3/buy-button.js">
</script>
<stripe-buy-button
buy-button-id="buy_btn_1QZkdjF7XLGd2a5gUp7oO0Xl"
publishable-key="pk_live_51ODgd2F7XLGd2a5gWi8b4cBLoKrc2gr3nLHkvZolog1I0qcGadqsgDAhTAERs6ocR1UwebkhVsOQ7yn5YHdZ320r00dYH3oVr2"
>
</stripe-buy-button>
</div>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h3 class="text-1xl font-bold text-gray-800 mb-2">Privacy or whatever</h3>
<ul>
<li>1. Although processing happens server-side, we don't keep your uploaded source code. It's stored in a temporary
directory then cleaned up after. Might port this to be a WASM project to learn it, if it's possible to do so, so all processing happens
on your device and I don't risk getting rate limited by pypi.org.
</li>
<br>
<li>2. Only normal browser logs are kept in rotation to see if something is broken. You specificically are not tracked.</li>
<li>
</li>
</ul>
</div>
</div>
</div>
<script>
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const resultsContainer = document.getElementById('resultsContainer');
const loadingIndicator = document.getElementById('loadingIndicator');
const results = document.getElementById('results');
const downloadBtn = document.getElementById('downloadBtn');
let currentResults = null;
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.add('dragover');
});
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.remove('dragover');
});
});
dropZone.addEventListener('drop', handleDrop);
dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileSelect);
function handleDrop(e) {
const dt = e.dataTransfer;
const file = dt.files[0];
handleFile(file);
}
function handleFileSelect(e) {
const file = e.target.files[0];
handleFile(file);
}
function handleFile(file) {
if (!file) return;
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
alert('File size exceeds 10MB limit');
return;
}
if (!file.name.endsWith('.py') && !file.name.endsWith('.zip')) {
alert('Please upload a Python file (.py) or ZIP archive');
return;
}
const formData = new FormData();
formData.append('file', file);
resultsContainer.classList.remove('hidden');
loadingIndicator.classList.remove('hidden');
results.innerHTML = '';
downloadBtn.classList.add('hidden');
fetch('/analyze', {
method: 'POST',
body: formData,
headers: {
'X-Last-Modified-Date': new Date(file.lastModified).toUTCString()
}
})
.then(response => response.json())
.then(data => {
loadingIndicator.classList.add('hidden');
if (data.error) {
results.innerHTML = `<div class="text-red-500">${data.error}</div>`;
return;
}
currentResults = data;
let output = '';
if (data.interpreter_version){
console.log(data.interpreter_version)
output += `<div class="mb-4"><strong>Vermin reported min Python Version: ${data.interpreter_version}</strong><br>`
output += '</div>';
}
if (data.file_list && data.file_list.length > 0) {
output += '<div class="mb-4"><strong>File List:</strong><br>';
data.file_list.forEach(fi => {
output += `${fi}<br>`;
});
output += '</div>';
}
if (data.requirements && data.requirements.length > 0) {
output += '<div class="mb-4"><strong>Requirements:</strong><br>';
data.requirements.forEach(req => {
output += `${req}<br>`;
});
output += '</div>';
}
if (data.unresolved && data.unresolved.length > 0) {
output += '<div class="text-yellow-600"><strong>Unresolved Imports:</strong><br>';
data.unresolved.forEach(imp => {
output += `${imp}<br>`;
});
output += '</div>';
}
if (data.reference_date) {
output += `<div class="text-gray-500 mt-4">Reference Date: ${new Date(data.reference_date).toLocaleString()}</div>`;
}
results.innerHTML = output;
downloadBtn.classList.remove('hidden');
})
.catch(error => {
loadingIndicator.classList.add('hidden');
results.innerHTML = `<div class="text-red-500">Error: ${error.message}</div>`;
});
}
downloadBtn.addEventListener('click', () => {
if (!currentResults) return;
fetch('/download', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(currentResults)
})
.then(response => response.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'requirements.txt';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
a.remove();
})
.catch(error => {
alert('Error downloading file: ' + error.message);
});
});
</script>
</body>
</html>