254 lines
10 KiB
HTML
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> |