initial commit

This commit is contained in:
ilia.gurielidze 2025-05-16 10:18:55 +04:00
commit 192ada8e94
8137 changed files with 1356931 additions and 0 deletions

75
README.md Normal file
View File

@ -0,0 +1,75 @@
# SCADA vs DWG Manifest Comparison Tool
A Flask web application to compare and reconcile mechanical manifest data (chutes and conveyors) against extracted device data from DWG files and SCADA object metadata.
## Features
- Upload Excel files containing manifest and DWG data
- Specify a GitHub repository URL containing SCADA JSON files
- Compare names across all three data sources
- Normalize names for consistent comparison (uppercase, strip whitespace)
- View discrepancies in a user-friendly tabbed interface
- Re-upload updated files for comparison against the same repository
- Persistent storage of repository clones
## Installation
1. Clone this repository:
```
git clone <repository-url>
cd <repository-directory>
```
2. Install the required dependencies:
```
pip install -r requirements.txt
```
## Usage
1. Start the Flask application:
```
flask run
```
2. Open your web browser and navigate to `http://127.0.0.1:5000/`
3. On the homepage:
- Enter the GitHub repository URL containing SCADA JSON files
- Upload your Excel file containing the manifest data (must include a "Name" column)
- Upload your Excel file containing the DWG data (must include a "Name" column)
- Click "Compare" to proceed
4. View the comparison results in the tabbed interface:
- SCADA vs DWG: Compare SCADA names with DWG names
- DWG vs Manifest: Compare DWG names with manifest names
- SCADA vs Manifest: Compare SCADA names with manifest names
5. To update files for re-comparison:
- Use the form at the bottom of the results page
- Upload new versions of the manifest and/or DWG files
- The application will automatically pull the latest changes from the repository
## Data Normalization
All names from the three sources are normalized for consistent comparison:
- Leading and trailing whitespace is removed
- All text is converted to uppercase
- Duplicate names are removed from each source
## Directory Structure
```
project/
├─ app.py # Main Flask application
├─ requirements.txt # Python dependencies
├─ README.md # This documentation
├─ clones/ # Repository clones storage
├─ templates/ # HTML templates
│ ├─ base.html # Base template with common layout
│ ├─ index.html # Homepage with upload form
│ └─ results.html # Results page with comparison tabs
└─ static/ # Static assets
├─ css/ # CSS stylesheets
└─ js/ # JavaScript files
```

Binary file not shown.

1406
app.py Normal file

File diff suppressed because it is too large Load Diff

17
requirements.txt Normal file
View File

@ -0,0 +1,17 @@
Flask==2.0.1
pandas>=1.5.0
numpy>=1.23.0
openpyxl>=3.0.9
GitPython>=3.1.24
flask-wtf==1.0.0
Werkzeug==2.0.1
Jinja2==3.0.1
itsdangerous==2.0.1
click==8.0.1
MarkupSafe==2.0.1
python-dateutil==2.8.2
pytz==2021.1
six==1.16.0
gitdb==4.0.7
smmap==4.0.0
et-xmlfile==1.1.0

@ -0,0 +1 @@
Subproject commit 69f65b3be7177235abb2c31fd224ee066a4448ea

266
static/css/style.css Normal file
View File

@ -0,0 +1,266 @@
/* Main Styles for SCADA vs DWG Manifest Comparison Tool */
/* General Styles */
body {
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
color: #333;
background-color: #f8f9fa;
padding-bottom: 60px; /* Space for footer */
}
/* Container Styles */
.container {
max-width: 1200px;
}
.container-fluid {
padding-left: 2rem;
padding-right: 2rem;
}
/* On extra large screens, add maximum width to prevent content from being too stretched */
@media (min-width: 2000px) {
.container-fluid {
max-width: 1800px;
margin-left: auto;
margin-right: auto;
}
}
/* On small screens, reduce padding for more content space */
@media (max-width: 768px) {
.container-fluid {
padding-left: 1rem;
padding-right: 1rem;
}
}
/* Card Styles */
.card {
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
border-radius: 0.5rem;
border: none;
margin-bottom: 1.5rem;
}
.card-header {
border-radius: 0.5rem 0.5rem 0 0 !important;
font-weight: 500;
}
.card-footer {
background-color: #f8f9fa;
border-top: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0 0 0.5rem 0.5rem !important;
}
/* Table Styles */
.table-container {
max-height: 500px;
overflow-y: auto;
border-radius: 0.25rem;
}
.table {
margin-bottom: 0;
}
.table-fixed {
table-layout: fixed;
}
.table-fixed td, .table-fixed th {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table-fixed td:hover {
white-space: normal;
word-wrap: break-word;
}
.table thead th {
position: sticky;
top: 0;
background-color: #fff;
z-index: 1;
border-bottom: 2px solid #dee2e6;
}
/* Highlight Styles */
.missing-item {
background-color: #ffebee !important;
}
.table tr.missing-item td {
border-color: #ffcdd2;
}
.summary-box {
border-radius: 0.5rem;
padding: 1.25rem;
margin-bottom: 1.25rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.05);
}
.bg-light-primary {
background-color: #e3f2fd;
}
/* Form Styles */
.form-control:focus {
border-color: #80bdff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.btn-primary {
background-color: #007bff;
border-color: #007bff;
}
.btn-primary:hover {
background-color: #0069d9;
border-color: #0062cc;
}
/* Navigation Styles */
.navbar {
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
margin-bottom: 1.5rem;
}
.nav-tabs .nav-link {
color: #495057;
border: 1px solid transparent;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
font-weight: 500;
}
.nav-tabs .nav-link.active {
color: #007bff;
background-color: #fff;
border-color: #dee2e6 #dee2e6 #fff;
}
.nav-tabs .nav-link:hover {
border-color: #e9ecef #e9ecef #dee2e6;
}
/* Footer Styles */
.footer {
position: fixed;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
background-color: #f8f9fa;
border-top: 1px solid #dee2e6;
z-index: 100;
}
/* Responsive Adjustments */
@media (max-width: 767.98px) {
.container {
padding-left: 10px;
padding-right: 10px;
}
.card-body {
padding: 1rem;
}
.table-container {
max-height: 400px;
}
.table td, .table th {
padding: 0.5rem;
}
body {
padding-bottom: 70px;
}
}
/* Print Styles */
@media print {
.no-print {
display: none !important;
}
.table-container {
max-height: none;
overflow: visible;
}
.card {
box-shadow: none;
border: 1px solid #dee2e6;
}
.missing-item {
background-color: #ffebee !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
/* General styles */
body {
padding-bottom: 70px; /* Space for the footer */
}
/* Table styles */
.table-responsive {
overflow-x: auto;
}
.table th {
position: sticky;
top: 0;
background-color: #f8f9fa;
}
/* Comparison highlighting */
.missing {
background-color: #f8d7da; /* Bootstrap danger light */
color: #842029;
}
.extra {
background-color: #d1e7dd; /* Bootstrap success light */
color: #0f5132;
}
.changed {
background-color: #fff3cd; /* Bootstrap warning light */
color: #664d03;
}
/* Badge styles for counts */
.badge-count {
font-size: 0.8em;
vertical-align: middle;
}
/* Tab panel styling */
.tab-content {
border-left: 1px solid #dee2e6;
border-right: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
padding: 20px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.card-header h4 {
font-size: 1.2rem;
}
.table {
font-size: 0.9rem;
}
}

183
static/js/original_names.js Normal file
View File

@ -0,0 +1,183 @@
/**
* This script converts normalized component names to their original form
* using the actual original names from the data source.
*/
// Function to get the original name from normalized name
function getOriginalName(normalizedName, sourceType) {
if (!window.nameMappings || !window.nameMappings[sourceType]) {
return normalizedName;
}
const mapping = window.nameMappings[sourceType][normalizedName];
if (!mapping) {
return normalizedName;
}
return mapping.originalName || normalizedName;
}
// Function to get the control panel from normalized name
function getControlPanel(normalizedName, sourceType) {
if (!window.nameMappings || !window.nameMappings[sourceType]) {
return "";
}
const mapping = window.nameMappings[sourceType][normalizedName];
if (!mapping) {
return "";
}
return mapping.controlPanel || "";
}
document.addEventListener('DOMContentLoaded', function() {
// Only proceed if we have the mappings
if (!window.nameMappings) {
console.warn('Name mappings not found');
return;
}
// Find all tables showing component names
const tables = document.querySelectorAll('.table-container table');
tables.forEach(table => {
const rows = table.querySelectorAll('tbody tr');
rows.forEach(row => {
const cells = row.querySelectorAll('td');
if (cells.length >= 1) {
const nameCell = cells[0];
const controlPanelCell = cells.length > 1 ? cells[1] : null;
if (nameCell) {
const normalizedName = nameCell.textContent.trim();
// Determine which source to use based on which tab we're in
let sourceType = '';
const tabPanel = table.closest('.tab-pane');
if (tabPanel) {
const tabId = tabPanel.id;
if (tabId === 'scada-dwg') {
if (table.closest('.card').querySelector('h5').textContent.includes('SCADA')) {
sourceType = 'scada';
} else {
sourceType = 'dwg';
}
} else if (tabId === 'manifest-dwg') {
if (table.closest('.card').querySelector('h5').textContent.includes('Manifest')) {
sourceType = 'manifest';
} else {
sourceType = 'dwg';
}
}
}
if (sourceType && window.nameMappings[sourceType] && window.nameMappings[sourceType][normalizedName]) {
const mapping = window.nameMappings[sourceType][normalizedName];
// Update the name cell with original name
if (mapping.originalName) {
nameCell.textContent = mapping.originalName;
}
// Update the control panel cell if needed (in case it was empty) and only if it exists
if (controlPanelCell && mapping.controlPanel && !controlPanelCell.textContent.trim()) {
controlPanelCell.textContent = mapping.controlPanel;
}
}
}
}
});
});
// Add header note to explain what's displayed
document.querySelectorAll('.card-header').forEach(header => {
if (header.querySelector('h5')) {
const note = document.createElement('div');
note.className = 'small text-light mt-1';
note.textContent = 'Names shown in their original format from source data';
header.appendChild(note);
}
});
// Add CSS styles for the original names
const style = document.createElement('style');
style.textContent = `
.original-name {
font-weight: bold;
color: #198754;
}
`;
document.head.appendChild(style);
// Add a debug section to help troubleshoot name lookups if needed
if (window.location.search.includes('debug=1')) {
const debugArea = document.createElement('div');
debugArea.className = 'card mt-4';
debugArea.innerHTML = `
<div class="card-header bg-secondary text-white">
<h5>Name Mapping Debug</h5>
</div>
<div class="card-body">
<p>Enter a normalized name to look up its original form:</p>
<div class="input-group mb-3">
<input type="text" class="form-control" id="debug-lookup-input" placeholder="Enter normalized name (with underscores)">
<select class="form-select" id="debug-source-select">
<option value="scada">SCADA</option>
<option value="manifest">Manifest</option>
<option value="dwg">DWG</option>
</select>
<button class="btn btn-outline-secondary" type="button" id="debug-lookup-btn">Lookup</button>
</div>
<div id="debug-result" class="alert alert-info" style="display:none;"></div>
<hr>
<details>
<summary>View all mappings</summary>
<pre id="debug-all-mappings" style="max-height:400px;overflow:auto;"></pre>
</details>
</div>
`;
document.querySelector('.container-fluid').appendChild(debugArea);
// Fill mappings data
const debugMappings = document.getElementById('debug-all-mappings');
debugMappings.textContent = JSON.stringify(window.nameMappings, null, 2);
// Set up lookup functionality
document.getElementById('debug-lookup-btn').addEventListener('click', function() {
const input = document.getElementById('debug-lookup-input').value.trim();
const sourceType = document.getElementById('debug-source-select').value;
const result = document.getElementById('debug-result');
if (input) {
const mapping = window.nameMappings[sourceType] && window.nameMappings[sourceType][input];
if (mapping) {
result.innerHTML = `
<strong>Original name:</strong> ${mapping.originalName || input}<br>
<strong>Control Panel:</strong> ${mapping.controlPanel || 'Not specified'}<br>
<strong>Source:</strong> ${sourceType.toUpperCase()}
`;
} else {
result.textContent = `No mapping found for "${input}" in ${sourceType.toUpperCase()} source`;
}
result.style.display = 'block';
}
});
}
// Add tooltips to table cells if needed
const nameCells = document.querySelectorAll('td.item-name');
nameCells.forEach(cell => {
const normalizedName = cell.textContent.trim();
const sourceType = cell.dataset.source; // 'scada', 'manifest', or 'dwg'
const originalName = getOriginalName(normalizedName, sourceType);
if (originalName && originalName !== normalizedName) {
cell.title = `Original: ${originalName}`;
}
});
});

402
static/js/script.js Normal file
View File

@ -0,0 +1,402 @@
/**
* SCADA vs DWG Manifest Comparison Tool
* Main JavaScript functionality
*/
document.addEventListener('DOMContentLoaded', function() {
// Initialize Bootstrap components
initializeBootstrapComponents();
// Add search/filter functionality to tables
initializeTableFilters();
// Add copy to clipboard functionality
initializeClipboardCopy();
// Handle form submissions to prevent resubmission prompts
initializeFormHandlers();
});
/**
* Initialize form handlers to prevent resubmission prompts
*/
function initializeFormHandlers() {
// Handle main comparison form
const comparisonForm = document.querySelector('form[action*="compare"]');
if (comparisonForm) {
comparisonForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
// Show loading indicator
const submitBtn = this.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = 'Processing...';
fetch(this.action, {
method: 'POST',
body: formData,
redirect: 'manual'
})
.then(response => {
if (response.type === 'opaqueredirect') {
// Server redirected, follow the redirect
window.location.href = response.url || '/';
return;
}
return response.text();
})
.then(html => {
if (html) {
// If we got HTML back and not a redirect, handle it
// This is a fallback in case redirect didn't work
window.location.reload();
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred during form submission. Please try again.');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
});
});
}
// Handle update files form
const updateForm = document.querySelector('form[action*="update_files"]');
if (updateForm) {
updateForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
// Show loading indicator
const submitBtn = this.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = 'Updating...';
fetch(this.action, {
method: 'POST',
body: formData,
redirect: 'manual'
})
.then(response => {
if (response.type === 'opaqueredirect') {
// Server redirected, follow the redirect
window.location.href = response.url || '/';
return;
}
return response.text();
})
.then(html => {
if (html) {
// If we got HTML back and not a redirect, handle it
window.location.reload();
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred during update. Please try again.');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
});
});
}
// Handle delete comparison form (the one created dynamically in the results page)
document.addEventListener('submit', function(e) {
if (e.target.action && e.target.action.includes('delete_comparison')) {
e.preventDefault();
fetch(e.target.action, {
method: 'POST',
redirect: 'manual'
})
.then(response => {
if (response.type === 'opaqueredirect') {
window.location.href = response.url || '/';
return;
}
return response.text();
})
.then(html => {
if (html) {
window.location.href = '/';
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the comparison. Please try again.');
});
}
});
}
/**
* Initialize Bootstrap components like tabs
*/
function initializeBootstrapComponents() {
// Initialize tabs if they exist
const tabEls = document.querySelectorAll('button[data-bs-toggle="tab"]');
if (tabEls.length > 0) {
tabEls.forEach(tabEl => {
new bootstrap.Tab(tabEl);
});
// Add event listener for tab change to persist active tab
tabEls.forEach(tab => {
tab.addEventListener('shown.bs.tab', function (event) {
// Store the currently active tab in local storage
localStorage.setItem('activeTab', event.target.id);
});
});
// Restore active tab from localStorage if available
const activeTabId = localStorage.getItem('activeTab');
if (activeTabId) {
const activeTab = document.getElementById(activeTabId);
if (activeTab) {
new bootstrap.Tab(activeTab).show();
}
}
}
}
/**
* Add search/filter functionality to tables
*/
function initializeTableFilters() {
// Create filter inputs for each table that needs filtering
const tableContainers = document.querySelectorAll('.table-container');
tableContainers.forEach((container, index) => {
const table = container.querySelector('table');
if (!table) return;
// Make the table the first element in a new structure
const originalHTML = container.innerHTML;
container.innerHTML = '';
// Create a wrapper for the sticky elements
const stickyHeader = document.createElement('div');
stickyHeader.className = 'sticky-table-header';
// Create filter input
const filterDiv = document.createElement('div');
filterDiv.className = 'table-filter-container d-flex justify-content-between align-items-center';
// Create the left side with search input
const searchGroup = document.createElement('div');
searchGroup.className = 'input-group';
searchGroup.style.flexBasis = '80%';
const filterInput = document.createElement('input');
filterInput.type = 'text';
filterInput.className = 'form-control table-filter';
filterInput.placeholder = 'Filter items...';
filterInput.setAttribute('data-table-index', index);
const filterClearBtn = document.createElement('button');
filterClearBtn.className = 'btn btn-outline-secondary';
filterClearBtn.innerHTML = '&times;';
filterClearBtn.type = 'button';
searchGroup.appendChild(filterInput);
searchGroup.appendChild(filterClearBtn);
// Add the search group to the filter container
filterDiv.appendChild(searchGroup);
// The copy button will be added by initializeClipboardCopy()
stickyHeader.appendChild(filterDiv);
// Create a scrollable content div
const scrollableContent = document.createElement('div');
scrollableContent.className = 'scrollable-table-content';
scrollableContent.innerHTML = originalHTML;
// Add both elements to the container
container.appendChild(stickyHeader);
container.appendChild(scrollableContent);
// Fix for header width issue - ensure the table has a consistent width
// and the header cells match the body cells
window.addEventListener('load', function() {
const tableHeader = scrollableContent.querySelector('thead');
const tableBody = scrollableContent.querySelector('tbody');
if (tableHeader && tableBody) {
// Ensure equal cell widths
const headerCells = tableHeader.querySelectorAll('th');
const firstRowCells = tableBody.querySelector('tr') ?
tableBody.querySelector('tr').querySelectorAll('td') : null;
if (headerCells.length > 0 && firstRowCells && firstRowCells.length > 0) {
// Set explicit widths on header cells based on first row
for (let i = 0; i < headerCells.length && i < firstRowCells.length; i++) {
const width = firstRowCells[i].offsetWidth;
headerCells[i].style.minWidth = width + 'px';
}
}
}
});
// Add event listeners
filterInput.addEventListener('input', function() {
// Get the table from the scrollable content
const tableInContent = scrollableContent.querySelector('table');
filterTable(tableInContent, this.value);
});
filterClearBtn.addEventListener('click', function() {
filterInput.value = '';
const tableInContent = scrollableContent.querySelector('table');
filterTable(tableInContent, '');
});
});
}
/**
* Filter table rows based on input
*/
function filterTable(table, filterText) {
const tbody = table.querySelector('tbody');
if (!tbody) return;
const rows = tbody.querySelectorAll('tr');
const lowerFilterText = filterText.toLowerCase();
rows.forEach(row => {
const text = row.textContent.toLowerCase();
if (text.includes(lowerFilterText)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
// Update counter if there is one
const cardHeader = table.closest('.card').querySelector('.card-header h5');
if (cardHeader) {
const originalText = cardHeader.getAttribute('data-original-text');
if (!originalText) {
cardHeader.setAttribute('data-original-text', cardHeader.textContent);
}
if (filterText) {
const visibleRowCount = [...rows].filter(row => row.style.display !== 'none').length;
const matches = cardHeader.textContent.match(/\((\d+)\)/);
if (matches && matches[1]) {
cardHeader.textContent = cardHeader.textContent.replace(
`(${matches[1]})`,
`(${visibleRowCount} of ${matches[1]})`
);
}
} else if (originalText) {
cardHeader.textContent = originalText;
}
}
}
/**
* Add ability to copy table contents to clipboard
*/
function initializeClipboardCopy() {
const tableContainers = document.querySelectorAll('.table-container');
tableContainers.forEach(container => {
// Find the filter container in the sticky header
const filterContainer = container.querySelector('.table-filter-container');
if (!filterContainer) return;
// Create copy button
const copyBtn = document.createElement('button');
copyBtn.className = 'btn btn-primary ms-2 copy-btn';
copyBtn.innerHTML = '<i class="bi bi-clipboard"></i> Copy';
// Insert button into the filter container
filterContainer.appendChild(copyBtn);
// Add event listener
copyBtn.addEventListener('click', function() {
// Find the table in the scrollable content
const scrollableContent = container.querySelector('.scrollable-table-content');
const table = scrollableContent ? scrollableContent.querySelector('table') : container.querySelector('table');
if (!table) return;
let text = '';
const rows = table.querySelectorAll('tbody tr');
rows.forEach(row => {
if (row.style.display !== 'none') {
const cells = row.querySelectorAll('td');
cells.forEach(cell => {
text += cell.textContent.trim() + '\t';
});
text += '\n';
}
});
copyToClipboard(text);
// Visual feedback
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="bi bi-check"></i> Copied!';
copyBtn.classList.add('btn-success');
copyBtn.classList.remove('btn-primary');
setTimeout(() => {
copyBtn.innerHTML = originalText;
copyBtn.classList.remove('btn-success');
copyBtn.classList.add('btn-primary');
}, 1500);
});
});
}
/**
* Helper function to copy text to clipboard
*/
function copyToClipboard(text) {
// Create a temporary textarea
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
// Select and copy
textarea.select();
document.execCommand('copy');
// Clean up
document.body.removeChild(textarea);
}
/**
* Handle file upload previews and validation
*/
document.querySelectorAll('input[type="file"]').forEach(fileInput => {
fileInput.addEventListener('change', function(e) {
const fileName = e.target.files[0]?.name;
const fileLabel = fileInput.nextElementSibling;
if (fileName) {
// Show the filename
if (fileLabel) {
fileLabel.textContent = fileName;
}
// Basic validation for Excel files
if (!fileName.endsWith('.xlsx') && !fileName.endsWith('.xls')) {
alert('Please upload a valid Excel file (.xlsx or .xls)');
fileInput.value = '';
if (fileLabel) {
fileLabel.textContent = 'Choose file';
}
}
}
});
});

BIN
temp_uploads/dwg.xlsx Normal file

Binary file not shown.

BIN
temp_uploads/manifest.xlsx Normal file

Binary file not shown.

58
templates/base.html Normal file
View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}SCADA vs DWG vs Manifest Comparison Tool{% endblock %}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">SCADA vs DWG vs Manifest</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('index') }}">Home</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="container-fluid mt-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<!-- Footer -->
<footer class="footer mt-5 py-3 bg-light">
<div class="container-fluid text-center">
<span class="text-muted">SCADA vs DWG vs Manifest Comparison Tool</span>
</div>
</footer>
<!-- Bootstrap JS with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JS -->
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
<script src="{{ url_for('static', filename='js/original_names.js') }}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>

209
templates/index.html Normal file
View File

@ -0,0 +1,209 @@
{% extends "base.html" %}
{% block title %}Home - SCADA vs DWG Manifest Comparison Tool{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
{% if has_previous_results %}
<div class="card mb-4">
<div class="card-header bg-info text-white">
<h4 class="mb-0">Previous Comparisons</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
<select id="comparison-selector" class="form-select form-select-lg mb-3">
<option value="">-- Select a comparison --</option>
{% for comparison_id, comparison in comparisons.items() %}
<option value="{{ comparison_id }}">{{ comparison.name }} ({{ comparison.timestamp }})</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<div class="d-grid gap-2">
<button id="view-comparison-btn" class="btn btn-success" disabled>
<i class="fas fa-chart-bar"></i> View Selected Comparison
</button>
</div>
</div>
</div>
<div id="comparison-actions" class="row mt-3" style="display: none;">
<div class="col-md-8">
<div class="input-group">
<input type="text" id="comparison-name" class="form-control" placeholder="Rename comparison">
<button id="rename-comparison-btn" class="btn btn-outline-secondary">
<i class="fas fa-edit"></i> Rename
</button>
</div>
</div>
<div class="col-md-4">
<div class="d-grid gap-2">
<button id="delete-comparison-btn" class="btn btn-danger">
<i class="fas fa-trash"></i> Delete Comparison
</button>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Compare SCADA, DWG, and Manifest Data</h4>
</div>
<div class="card-body">
<p class="lead">Upload your data sources to identify discrepancies between SCADA object metadata, DWG-extracted device list, and mechanical manifest data.</p>
<form action="{{ url_for('compare') }}" method="post" enctype="multipart/form-data" class="mb-4">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label for="repo_url" class="form-label">Git Repository URL containing SCADA JSON files</label>
<input type="url" class="form-control" id="repo_url" name="repo_url" placeholder="http://192.168.5.191:3000/LCI/MTN6.git" required>
<div class="form-text">Enter the URL of the Git repository containing SCADA object metadata JSON files.</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="manifest_file" class="form-label">Mechanical Manifest Excel File</label>
<input type="file" class="form-control" id="manifest_file" name="manifest_file" accept=".xlsx" required>
<div class="form-text">Upload Excel file containing the mechanical manifest data (must include a "Name" column).</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="dwg_file" class="form-label">DWG-Extracted Device List Excel File</label>
<input type="file" class="form-control" id="dwg_file" name="dwg_file" accept=".xlsx" required>
<div class="form-text">Upload Excel file containing the DWG-extracted device list (must include a "Name" column).</div>
</div>
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Compare Data Sources</button>
</div>
</form>
</div>
<div class="card-footer">
<p class="mb-0"><small>Note: All names will be normalized for consistent comparison (uppercase, strip whitespace).</small></p>
<p class="mb-0 mt-2"><small class="text-muted">Uploaded files and comparison results are shared with all users of this application.</small></p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// Store CSRF token for JavaScript use
const csrfToken = "{{ csrf_token() }}";
document.addEventListener('DOMContentLoaded', function() {
const comparisonSelector = document.getElementById('comparison-selector');
const viewComparisonBtn = document.getElementById('view-comparison-btn');
const comparisonActions = document.getElementById('comparison-actions');
const comparisonNameInput = document.getElementById('comparison-name');
const renameComparisonBtn = document.getElementById('rename-comparison-btn');
const deleteComparisonBtn = document.getElementById('delete-comparison-btn');
if (comparisonSelector) {
comparisonSelector.addEventListener('change', function() {
const selectedValue = this.value;
if (selectedValue) {
viewComparisonBtn.disabled = false;
comparisonActions.style.display = 'flex';
// Get the selected option text (comparison name)
const selectedOption = this.options[this.selectedIndex];
const comparisonName = selectedOption.text.split(' (')[0];
comparisonNameInput.value = comparisonName;
} else {
viewComparisonBtn.disabled = true;
comparisonActions.style.display = 'none';
}
});
}
if (viewComparisonBtn) {
viewComparisonBtn.addEventListener('click', function() {
const selectedValue = comparisonSelector.value;
if (selectedValue) {
window.location.href = '/comparison/' + selectedValue;
}
});
}
if (renameComparisonBtn) {
renameComparisonBtn.addEventListener('click', function() {
const selectedValue = comparisonSelector.value;
const newName = comparisonNameInput.value.trim();
if (selectedValue && newName) {
fetch('/rename_comparison/' + selectedValue, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': csrfToken
},
body: 'name=' + encodeURIComponent(newName)
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the option text
const selectedOption = comparisonSelector.options[comparisonSelector.selectedIndex];
const timestampPart = selectedOption.text.split(' (')[1];
selectedOption.text = newName + ' (' + timestampPart;
alert('Comparison renamed successfully!');
} else {
alert('Failed to rename comparison: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while renaming the comparison.');
});
}
});
}
if (deleteComparisonBtn) {
deleteComparisonBtn.addEventListener('click', function() {
const selectedValue = comparisonSelector.value;
if (selectedValue && confirm('Are you sure you want to delete this comparison? This action cannot be undone.')) {
// Use fetch API instead of form submission
fetch('/delete_comparison/' + selectedValue, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken
},
redirect: 'manual'
})
.then(response => {
if (response.type === 'opaqueredirect') {
window.location.href = response.url || '/';
return;
}
return response.text();
})
.then(html => {
if (html) {
window.location.reload();
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the comparison. Please try again.');
});
}
});
}
});
</script>
{% endblock %}

843
templates/results.html Normal file
View File

@ -0,0 +1,843 @@
{% extends "base.html" %}
{% block title %}Results - SCADA vs DWG Manifest Comparison Tool{% endblock %}
{% block extra_css %}
<style>
.missing-item {
background-color: #ffebee;
}
/* Main container for the table */
.table-container {
position: relative;
min-height: 200px;
border-top: none;
overflow: visible; /* Allow the inner content to determine scrolling */
}
/* Sticky header at the top */
.sticky-table-header {
position: sticky;
top: 0;
z-index: 20;
background-color: white;
border-bottom: 1px solid #dee2e6;
width: 100%;
}
/* Scrollable content area */
.scrollable-table-content {
overflow-y: auto;
max-height: 500px; /* Only this element should have scrolling */
min-height: 200px;
scrollbar-width: thin; /* For Firefox */
padding-bottom: 10px; /* Add some padding at bottom to ensure last row is visible */
}
/* For Webkit browsers like Chrome/Safari */
.scrollable-table-content::-webkit-scrollbar {
width: 8px;
}
/* Table filter inside sticky header */
.table-filter-container {
padding: 10px;
background-color: white;
border-bottom: 1px solid #dee2e6;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* Fixed table layout - prevents column width changes */
.table-fixed {
table-layout: fixed;
width: 100%;
}
.table-fixed td, .table-fixed th {
word-wrap: break-word;
padding: 8px;
}
/* Make table header sticky */
.scrollable-table-content thead {
position: sticky;
top: 0;
z-index: 10;
background-color: white;
width: 100%; /* Full width */
}
.scrollable-table-content thead tr {
display: table;
width: 100%;
table-layout: fixed;
}
.scrollable-table-content thead th {
background-color: white;
border-bottom: 2px solid #dee2e6;
box-shadow: 0 2px 5px rgba(0,0,0,0.15);
font-weight: bold;
padding: 10px 8px;
}
/* Remove border-spacing */
.scrollable-table-content table {
width: 100%;
border-collapse: collapse;
table-layout: fixed; /* Force fixed layout */
}
.summary-box {
border-radius: 5px;
padding: 15px;
margin-bottom: 20px;
}
.bg-light-primary {
background-color: #e3f2fd;
}
/* Make sure the table header has a white background that fully covers content */
.table > thead {
background-color: white;
}
/* Copy button styling */
.copy-btn {
min-width: 80px;
}
/* Additional styling to ensure header is fully visible */
.scrollable-table-content thead::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: white;
z-index: -1;
}
/* Fixed column layout */
.name-col {
width: 70%;
}
.control-panel-col {
width: 30%;
text-align: right;
}
/* Table structure */
.comparison-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed; /* Force fixed layout */
}
.comparison-table th:first-child,
.comparison-table td:first-child {
text-align: left;
padding-right: 10px;
}
.comparison-table th:last-child,
.comparison-table td:last-child {
text-align: right;
padding-left: 10px;
}
.comparison-table tr {
border-bottom: 1px solid #e0e0e0;
}
/* Ensure rows maintain proper layout */
.table tbody tr {
display: table;
width: 100%;
table-layout: fixed;
}
/* Reset any overflow settings on card containers */
.card, .card-body {
overflow: visible !important;
}
/* Main container for the table */
.table-container {
position: relative;
min-height: 200px;
border-top: none;
overflow: visible;
max-height: none;
}
/* Scrollable content area - ONLY this should have scrolling */
.scrollable-table-content {
overflow-y: auto;
max-height: 500px;
min-height: 200px;
scrollbar-width: thin; /* For Firefox */
padding-bottom: 10px;
}
/* Ensure parent containers don't create scrollbars */
.card, .card-body, .card-header, .card-footer {
overflow: visible !important;
}
/* Remove height constraints from parent containers */
.card.h-100 {
height: auto !important;
min-height: auto !important;
max-height: none !important;
}
/* Fixed table with scrollable body */
.table-container {
position: relative;
border-top: none;
overflow: hidden;
}
/* Set the table header to be sticky */
.table-fixed thead {
position: sticky;
top: 0;
z-index: 10;
background-color: white;
}
/* Make only the tbody scroll */
.scrollable-table-content {
overflow-y: auto;
max-height: 400px;
scrollbar-width: thin;
}
/* Remove any height constraints from parent cards */
.card.h-100 {
height: auto !important;
}
/* Ensure the table fills its container */
.table-fixed {
width: 100%;
margin-bottom: 0;
}
/* Give header cells proper styling */
.table-fixed th {
background-color: white;
position: sticky;
top: 0;
z-index: 10;
border-bottom: 2px solid #dee2e6;
}
/* COMPLETELY REWRITTEN TABLE STYLES */
/* Basic reset - clear previous styles */
.table-container, .scrollable-table-content, .card.h-100 {
max-height: none;
min-height: 0;
overflow: visible;
height: auto !important;
}
/* Table container - only for positioning */
.table-container {
position: relative;
border-top: none;
}
/* Scrollable content container - ONLY place with scrollbar */
.scrollable-table-content {
display: block;
width: 100%;
overflow-y: auto;
max-height: 400px; /* Adjust height as needed */
border: 1px solid #dee2e6;
}
/* Ensure table takes full width */
.table-fixed {
width: 100%;
margin-bottom: 0;
border-collapse: separate;
border-spacing: 0;
}
/* Make header stick to top */
.table-fixed thead {
position: sticky;
top: 0;
z-index: 5;
}
/* Header styling */
.table-fixed th {
background-color: #fff;
box-shadow: 0 1px 1px rgba(0,0,0,0.1);
border-bottom: 2px solid #dee2e6;
position: sticky;
top: 0;
}
/* Fix for last row being cut off */
.scrollable-table-content tbody tr:last-child td {
border-bottom: 1px solid #dee2e6;
}
/* Add bottom padding to ensure all content visible */
.scrollable-table-content {
padding-bottom: 1px;
}
</style>
{% endblock %}
{% block content %}
<!-- Store name mappings for JavaScript -->
<script>
// Store name mappings as a JavaScript object
window.nameMappings = {};
{% if data.name_mappings and data.name_mappings.scada %}
window.nameMappings.scada = {};
{% for key, value in data.name_mappings.scada.items() %}
window.nameMappings.scada["{{ key }}"] = {
originalName: {{ value.original_name|tojson }},
controlPanel: {{ value.control_panel|tojson }}
};
{% endfor %}
{% else %}
window.nameMappings.scada = {};
{% endif %}
{% if data.name_mappings and data.name_mappings.manifest %}
window.nameMappings.manifest = {};
{% for key, value in data.name_mappings.manifest.items() %}
window.nameMappings.manifest["{{ key }}"] = {
originalName: {{ value.original_name|tojson }},
controlPanel: {{ value.control_panel|tojson }}
};
{% endfor %}
{% else %}
window.nameMappings.manifest = {};
{% endif %}
{% if data.name_mappings and data.name_mappings.dwg %}
window.nameMappings.dwg = {};
{% for key, value in data.name_mappings.dwg.items() %}
window.nameMappings.dwg["{{ key }}"] = {
originalName: {{ value.original_name|tojson }},
controlPanel: {{ value.control_panel|tojson }}
};
{% endfor %}
{% else %}
window.nameMappings.dwg = {};
{% endif %}
</script>
<!-- Comparison Selector -->
{% if comparisons %}
<div class="card mb-4">
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
<h4 class="mb-0">Available Comparisons</h4>
<a href="{{ url_for('index') }}" class="btn btn-light btn-sm">Back to Upload</a>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
<select id="comparison-selector" class="form-select form-select-lg mb-3">
<option value="">-- Select a different comparison --</option>
{% for comparison_id, comparison in comparisons.items() %}
<option value="{{ comparison_id }}" {% if comparison_id == data.comparison_id %}selected{% endif %}>
{{ comparison.name }} ({{ comparison.timestamp }})
</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<div class="d-grid gap-2">
<button id="view-comparison-btn" class="btn btn-success">
<i class="fas fa-chart-bar"></i> View Selected Comparison
</button>
</div>
</div>
</div>
<div id="comparison-actions" class="row mt-3">
<div class="col-md-8">
<div class="input-group">
<input type="text" id="comparison-name" class="form-control" placeholder="Rename comparison" value="{{ data.name }}">
<button id="rename-comparison-btn" class="btn btn-outline-secondary">
<i class="fas fa-edit"></i> Rename
</button>
</div>
</div>
<div class="col-md-4">
<div class="d-grid gap-2">
<button id="delete-comparison-btn" class="btn btn-danger">
<i class="fas fa-trash"></i> Delete Comparison
</button>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="row mb-4">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Comparison Results: {{ data.name }}</h4>
</div>
<div class="card-body">
<!-- Summary Statistics -->
<div class="summary-box bg-light-primary mb-4">
<div class="row">
<div class="col-md-4">
<h5>SCADA Items: {{ data.scada_vs_manifest.scada_count }}</h5>
</div>
<div class="col-md-4">
<h5>Manifest Items: {{ data.scada_vs_manifest.manifest_count }}</h5>
</div>
<div class="col-md-4">
<h5>DWG Items: {{ data.scada_vs_dwg.dwg_count }}</h5>
</div>
</div>
</div>
<!-- Repository Information -->
<div class="summary-box bg-light mb-4">
<div class="row">
<div class="col-md-6">
<h5>Repository: {{ data.repository_url }}</h5>
<p>Last updated: {{ data.timestamp }}</p>
</div>
<div class="col-md-6 text-end">
<form id="refresh-repo-form" action="{{ url_for('refresh_repository') }}" method="post">
<input type="hidden" name="repo_id" value="{{ data.repo_id }}">
<input type="hidden" name="comparison_id" value="{{ data.comparison_id }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-outline-primary">
<i class="fas fa-sync-alt"></i> Refresh Repository & Reload Data
</button>
</form>
</div>
</div>
</div>
<!-- Tabs Navigation -->
<ul class="nav nav-tabs" id="comparisonTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="scada-dwg-tab" data-bs-toggle="tab" data-bs-target="#scada-dwg" type="button" role="tab" aria-controls="scada-dwg" aria-selected="true">SCADA vs DWG</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="manifest-dwg-tab" data-bs-toggle="tab" data-bs-target="#manifest-dwg" type="button" role="tab" aria-controls="manifest-dwg" aria-selected="false">Manifest vs DWG</button>
</li>
</ul>
<!-- Tab Content -->
<div class="tab-content mt-3" id="comparisonTabsContent">
<!-- SCADA vs DWG Tab -->
<div class="tab-pane fade show active" id="scada-dwg" role="tabpanel" aria-labelledby="scada-dwg-tab">
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-danger text-white">
<h5 class="mb-0">In SCADA but Missing from DWG ({{ data.scada_vs_dwg.only_in_scada|length }})</h5>
</div>
<div class="card-body table-container">
{% if data.scada_vs_dwg.only_in_scada %}
<div class="scrollable-table-content">
<table class="table table-striped table-bordered table-fixed">
<thead>
<tr>
<th class="name-col">Name</th>
<th class="control-panel-col">Control Panel</th>
</tr>
</thead>
<tbody>
{% for item in data.scada_vs_dwg.only_in_scada %}
<tr class="missing-item">
<td class="name-col">{{ item.name }}</td>
<td class="control-panel-col">{{ item.control_panel }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-success">No items in SCADA are missing from DWG.</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-danger text-white">
<h5 class="mb-0">In DWG but Missing from SCADA ({{ data.scada_vs_dwg.only_in_dwg|length }})</h5>
</div>
<div class="card-body table-container">
{% if data.scada_vs_dwg.only_in_dwg %}
<div class="scrollable-table-content">
<table class="table table-striped table-bordered table-fixed">
<thead>
<tr>
<th class="name-col">Name</th>
<th class="control-panel-col">Control Panel</th>
</tr>
</thead>
<tbody>
{% for item in data.scada_vs_dwg.only_in_dwg %}
<tr class="missing-item">
<td class="name-col">{{ item.name }}</td>
<td class="control-panel-col">{{ item.control_panel }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-success">No items in DWG are missing from SCADA.</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Names Found in Both SCADA and DWG ({{ data.scada_vs_dwg.common|length }})</h5>
</div>
<div class="card-body table-container">
{% if data.scada_vs_dwg.common %}
<div class="scrollable-table-content">
<table class="table table-striped table-bordered table-fixed">
<thead>
<tr>
<th class="name-col">Name</th>
<th class="control-panel-col">Control Panel</th>
</tr>
</thead>
<tbody>
{% for item in data.scada_vs_dwg.common %}
<tr>
<td class="name-col">{{ item.name }}</td>
<td class="control-panel-col">{{ item.control_panel }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-danger">No common names found between SCADA and DWG.</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Manifest vs DWG Tab -->
<div class="tab-pane fade" id="manifest-dwg" role="tabpanel" aria-labelledby="manifest-dwg-tab">
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-danger text-white">
<h5 class="mb-0">In Manifest but Missing from DWG ({{ data.manifest_vs_dwg.only_in_manifest|length }})</h5>
</div>
<div class="card-body table-container">
{% if data.manifest_vs_dwg.only_in_manifest %}
<div class="scrollable-table-content">
<table class="table table-striped table-bordered table-fixed">
<thead>
<tr>
<th class="name-col">Name</th>
</tr>
</thead>
<tbody>
{% for item in data.manifest_vs_dwg.only_in_manifest %}
<tr class="missing-item">
<td class="name-col">{{ item.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-success">No items in Manifest are missing from DWG.</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-danger text-white">
<h5 class="mb-0">In DWG but Missing from Manifest ({{ data.manifest_vs_dwg.only_in_dwg|length }})</h5>
</div>
<div class="card-body table-container">
{% if data.manifest_vs_dwg.only_in_dwg %}
<div class="scrollable-table-content">
<table class="table table-striped table-bordered table-fixed">
<thead>
<tr>
<th class="name-col">Name</th>
</tr>
</thead>
<tbody>
{% for item in data.manifest_vs_dwg.only_in_dwg %}
<tr class="missing-item">
<td class="name-col">{{ item.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-success">No items in DWG are missing from Manifest.</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Names Found in Both Manifest and DWG ({{ data.manifest_vs_dwg.common|length }})</h5>
</div>
<div class="card-body table-container">
{% if data.manifest_vs_dwg.common %}
<div class="scrollable-table-content">
<table class="table table-striped table-bordered table-fixed">
<thead>
<tr>
<th class="name-col">Name</th>
</tr>
</thead>
<tbody>
{% for item in data.manifest_vs_dwg.common %}
<tr>
<td class="name-col">{{ item.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-danger">No common names found between Manifest and DWG.</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer">
<h5>Update Comparison</h5>
<form id="update-comparison-form" action="{{ url_for('update_files') }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="repo_id" value="{{ data.repo_id }}">
<input type="hidden" name="comparison_id" value="{{ data.comparison_id }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="manifest_file" class="form-label">New Manifest Excel File (Optional)</label>
<input type="file" class="form-control" id="manifest_file" name="manifest_file" accept=".xlsx">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="dwg_file" class="form-label">New DWG Excel File (Optional)</label>
<input type="file" class="form-control" id="dwg_file" name="dwg_file" accept=".xlsx">
</div>
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Update Comparison</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// Store CSRF token for JavaScript use
const csrfToken = "{{ csrf_token() }}";
// Initialize the Bootstrap tabs
document.addEventListener('DOMContentLoaded', function() {
// This ensures tabs work properly
var triggerTabList = [].slice.call(document.querySelectorAll('#comparisonTabs button'))
triggerTabList.forEach(function(triggerEl) {
new bootstrap.Tab(triggerEl);
});
// Handle the refresh repository form submission
const refreshForm = document.getElementById('refresh-repo-form');
if (refreshForm) {
refreshForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
// Update button text and disable
const submitBtn = this.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Refreshing...';
fetch(this.action, {
method: 'POST',
body: formData,
redirect: 'manual'
})
.then(response => {
if (response.type === 'opaqueredirect') {
window.location.href = response.url || '/';
return;
}
return response.text();
})
.then(html => {
if (html) {
// If we got HTML back, refresh the page
window.location.reload();
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while refreshing the repository. Please try again.');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
});
});
}
// Handle the update comparison form submission
const updateForm = document.getElementById('update-comparison-form');
if (updateForm) {
updateForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
// Show loading indicator
const submitBtn = this.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = 'Updating...';
fetch(this.action, {
method: 'POST',
body: formData,
redirect: 'manual'
})
.then(response => {
if (response.type === 'opaqueredirect') {
// Server redirected, follow the redirect
window.location.href = response.url || '/';
return;
}
return response.text();
})
.then(html => {
if (html) {
// If we got HTML back and not a redirect, handle it
window.location.reload();
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred during update. Please try again.');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
});
});
}
// Comparison selector functionality
const comparisonSelector = document.getElementById('comparison-selector');
const viewComparisonBtn = document.getElementById('view-comparison-btn');
const comparisonActions = document.getElementById('comparison-actions');
const comparisonNameInput = document.getElementById('comparison-name');
const renameComparisonBtn = document.getElementById('rename-comparison-btn');
const deleteComparisonBtn = document.getElementById('delete-comparison-btn');
if (viewComparisonBtn) {
viewComparisonBtn.addEventListener('click', function() {
const selectedValue = comparisonSelector.value;
if (selectedValue) {
window.location.href = '/comparison/' + selectedValue;
}
});
}
if (renameComparisonBtn) {
renameComparisonBtn.addEventListener('click', function() {
const selectedValue = comparisonSelector.value || '{{ data.comparison_id }}';
const newName = comparisonNameInput.value.trim();
if (selectedValue && newName) {
fetch('/rename_comparison/' + selectedValue, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': csrfToken
},
body: 'name=' + encodeURIComponent(newName)
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the option text
if (comparisonSelector) {
const selectedOption = comparisonSelector.options[comparisonSelector.selectedIndex];
const timestampPart = selectedOption.text.split(' (')[1];
selectedOption.text = newName + ' (' + timestampPart;
}
// Update the page title
const headerTitle = document.querySelector('.card-header h4');
if (headerTitle) {
headerTitle.textContent = 'Comparison Results: ' + newName;
}
alert('Comparison renamed successfully!');
} else {
alert('Failed to rename comparison: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while renaming the comparison.');
});
}
});
}
if (deleteComparisonBtn) {
deleteComparisonBtn.addEventListener('click', function() {
const selectedValue = comparisonSelector ? comparisonSelector.value : '{{ data.comparison_id }}';
if (selectedValue && confirm('Are you sure you want to delete this comparison? This action cannot be undone.')) {
// Use fetch API instead of form submission
fetch('/delete_comparison/' + selectedValue, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken
},
redirect: 'manual'
})
.then(response => {
if (response.type === 'opaqueredirect') {
window.location.href = response.url || '/';
return;
}
return response.text();
})
.then(html => {
if (html) {
window.location.href = '/';
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the comparison. Please try again.');
});
}
});
}
});
</script>
{% endblock %}

247
venv/bin/Activate.ps1 Normal file
View File

@ -0,0 +1,247 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)
<# Function declarations --------------------------------------------------- #>
<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}
# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}
# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}
# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}
# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}
# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}
<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }
if ($pyvenvConfigPath) {
Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]
# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}
$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}
<# Begin Activate script --------------------------------------------------- #>
# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}
# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}
Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"
# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
Write-Verbose "Setting prompt to '$Prompt'"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
$env:VIRTUAL_ENV_PROMPT = $Prompt
}
# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}
# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

63
venv/bin/activate Normal file
View File

@ -0,0 +1,63 @@
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV=/home/adminuser/scada_vs_dwg_manifest/project/venv
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/"bin":$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1='(venv) '"${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT='(venv) '
export VIRTUAL_ENV_PROMPT
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null

26
venv/bin/activate.csh Normal file
View File

@ -0,0 +1,26 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV /home/adminuser/scada_vs_dwg_manifest/project/venv
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
set prompt = '(venv) '"$prompt"
setenv VIRTUAL_ENV_PROMPT '(venv) '
endif
alias pydoc python -m pydoc
rehash

69
venv/bin/activate.fish Normal file
View File

@ -0,0 +1,69 @@
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
# (https://fishshell.com/); you cannot run it directly.
function deactivate -d "Exit virtual environment and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
set -e _OLD_FISH_PROMPT_OVERRIDE
# prevents error when using nested fish instances (Issue #93858)
if functions -q _old_fish_prompt
functions -e fish_prompt
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
end
set -e VIRTUAL_ENV
set -e VIRTUAL_ENV_PROMPT
if test "$argv[1]" != "nondestructive"
# Self-destruct!
functions -e deactivate
end
end
# Unset irrelevant variables.
deactivate nondestructive
set -gx VIRTUAL_ENV /home/adminuser/scada_vs_dwg_manifest/project/venv
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
# Unset PYTHONHOME if set.
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# Save the current fish_prompt function as the function _old_fish_prompt.
functions -c fish_prompt _old_fish_prompt
# With the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command.
set -l old_status $status
# Output the venv prompt; color taken from the blue of the Python logo.
printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
# Restore the return status of the previous command.
echo "exit $old_status" | .
# Output the original/"old" prompt.
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
set -gx VIRTUAL_ENV_PROMPT '(venv) '
end

8
venv/bin/f2py Executable file
View File

@ -0,0 +1,8 @@
#!/home/adminuser/scada_vs_dwg_manifest/project/venv/bin/python3.11
# -*- coding: utf-8 -*-
import re
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
venv/bin/flask Executable file
View File

@ -0,0 +1,8 @@
#!/home/adminuser/scada_vs_dwg_manifest/project/venv/bin/python3.11
# -*- coding: utf-8 -*-
import re
import sys
from flask.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
venv/bin/numpy-config Executable file
View File

@ -0,0 +1,8 @@
#!/home/adminuser/scada_vs_dwg_manifest/project/venv/bin/python3.11
# -*- coding: utf-8 -*-
import re
import sys
from numpy._configtool import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
venv/bin/pip Executable file
View File

@ -0,0 +1,8 @@
#!/home/adminuser/scada_vs_dwg_manifest/project/venv/bin/python3.11
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
venv/bin/pip3 Executable file
View File

@ -0,0 +1,8 @@
#!/home/adminuser/scada_vs_dwg_manifest/project/venv/bin/python3.11
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
venv/bin/pip3.11 Executable file
View File

@ -0,0 +1,8 @@
#!/home/adminuser/scada_vs_dwg_manifest/project/venv/bin/python3.11
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

1
venv/bin/python Symbolic link
View File

@ -0,0 +1 @@
python3.11

1
venv/bin/python3 Symbolic link
View File

@ -0,0 +1 @@
python3.11

1
venv/bin/python3.11 Symbolic link
View File

@ -0,0 +1 @@
/usr/bin/python3.11

View File

@ -0,0 +1 @@
pip

View File

@ -0,0 +1,28 @@
Copyright 2010 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,124 @@
Metadata-Version: 2.1
Name: Flask
Version: 2.0.1
Summary: A simple framework for building complex web applications.
Home-page: https://palletsprojects.com/p/flask
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://flask.palletsprojects.com/
Project-URL: Changes, https://flask.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/flask/
Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
Requires-Dist: Werkzeug (>=2.0)
Requires-Dist: Jinja2 (>=3.0)
Requires-Dist: itsdangerous (>=2.0)
Requires-Dist: click (>=7.1.2)
Provides-Extra: async
Requires-Dist: asgiref (>=3.2) ; extra == 'async'
Provides-Extra: dotenv
Requires-Dist: python-dotenv ; extra == 'dotenv'
Flask
=====
Flask is a lightweight `WSGI`_ web application framework. It is designed
to make getting started quick and easy, with the ability to scale up to
complex applications. It began as a simple wrapper around `Werkzeug`_
and `Jinja`_ and has become one of the most popular Python web
application frameworks.
Flask offers suggestions, but doesn't enforce any dependencies or
project layout. It is up to the developer to choose the tools and
libraries they want to use. There are many extensions provided by the
community that make adding new functionality easy.
.. _WSGI: https://wsgi.readthedocs.io/
.. _Werkzeug: https://werkzeug.palletsprojects.com/
.. _Jinja: https://jinja.palletsprojects.com/
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install -U Flask
.. _pip: https://pip.pypa.io/en/stable/quickstart/
A Simple Example
----------------
.. code-block:: python
# save this as app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, World!"
.. code-block:: text
$ flask run
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Contributing
------------
For guidance on setting up a development environment and how to make a
contribution to Flask, see the `contributing guidelines`_.
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
Donate
------
The Pallets organization develops and supports Flask and the libraries
it uses. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://flask.palletsprojects.com/
- Changes: https://flask.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Flask/
- Source Code: https://github.com/pallets/flask/
- Issue Tracker: https://github.com/pallets/flask/issues/
- Website: https://palletsprojects.com/p/flask/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

View File

@ -0,0 +1,52 @@
../../../bin/flask,sha256=Dh32gJlSNQLUUNuZ5Cq2GqxONlsMvsg5me7DQzbxq10,257
Flask-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Flask-2.0.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
Flask-2.0.1.dist-info/METADATA,sha256=50Jm1647RKw98p4RF64bCqRh0wajk-n3hQ7av2-pniA,3808
Flask-2.0.1.dist-info/RECORD,,
Flask-2.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
Flask-2.0.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
Flask-2.0.1.dist-info/entry_points.txt,sha256=gBLA1aKg0OYR8AhbAfg8lnburHtKcgJLDU52BBctN0k,42
Flask-2.0.1.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6
flask/__init__.py,sha256=w5v6GCNm8eLDMNWqs2ue7HLHo75aslAwz1h3k3YO9HY,2251
flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
flask/__pycache__/__init__.cpython-311.pyc,,
flask/__pycache__/__main__.cpython-311.pyc,,
flask/__pycache__/app.cpython-311.pyc,,
flask/__pycache__/blueprints.cpython-311.pyc,,
flask/__pycache__/cli.cpython-311.pyc,,
flask/__pycache__/config.cpython-311.pyc,,
flask/__pycache__/ctx.cpython-311.pyc,,
flask/__pycache__/debughelpers.cpython-311.pyc,,
flask/__pycache__/globals.cpython-311.pyc,,
flask/__pycache__/helpers.cpython-311.pyc,,
flask/__pycache__/logging.cpython-311.pyc,,
flask/__pycache__/scaffold.cpython-311.pyc,,
flask/__pycache__/sessions.cpython-311.pyc,,
flask/__pycache__/signals.cpython-311.pyc,,
flask/__pycache__/templating.cpython-311.pyc,,
flask/__pycache__/testing.cpython-311.pyc,,
flask/__pycache__/typing.cpython-311.pyc,,
flask/__pycache__/views.cpython-311.pyc,,
flask/__pycache__/wrappers.cpython-311.pyc,,
flask/app.py,sha256=q6lpiiWVxjljQRwjjneUBpfllXYPEq0CFAUpTQ5gIeA,82376
flask/blueprints.py,sha256=OjI-dkwx96ZNMUGDDFMKzgcpUJf240WRuMlHkmgI1Lc,23541
flask/cli.py,sha256=iN1pL2SevE5Nmvey-0WwnxG3nipZXIiE__Ed4lx3IuM,32036
flask/config.py,sha256=jj_7JGen_kYuTlKrx8ZPBsZddb8mihC0ODg4gcjXBX8,11068
flask/ctx.py,sha256=EM3W0v1ctuFQAGk_HWtQdoJEg_r2f5Le4xcmElxFwwk,17428
flask/debughelpers.py,sha256=wk5HtLwENsQ4e8tkxfBn6ykUeVRDuMbQCKgtEVe6jxk,6171
flask/globals.py,sha256=cWd-R2hUH3VqPhnmQNww892tQS6Yjqg_wg8UvW1M7NM,1723
flask/helpers.py,sha256=00WqA3wYeyjMrnAOPZTUyrnUf7H8ik3CVT0kqGl_qjk,30589
flask/json/__init__.py,sha256=d-db2DJMASq0G7CI-JvobehRE1asNRGX1rIDQ1GF9WM,11580
flask/json/__pycache__/__init__.cpython-311.pyc,,
flask/json/__pycache__/tag.cpython-311.pyc,,
flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857
flask/logging.py,sha256=1o_hirVGqdj7SBdETnhX7IAjklG89RXlrwz_2CjzQQE,2273
flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
flask/scaffold.py,sha256=EhQuiFrdcmJHxqPGQkEpqLsEUZ7ULZD0rtED2NrduvM,32400
flask/sessions.py,sha256=Kb7zY4qBIOU2cw1xM5mQ_KmgYUBDFbUYWjlkq0EFYis,15189
flask/signals.py,sha256=HQWgBEXlrLbHwLBoWqAStJKcN-rsB1_AMO8-VZ7LDOo,2126
flask/templating.py,sha256=l96VD39JQ0nue4Bcj7wZ4-FWWs-ppLxvgBCpwDQ4KAk,5626
flask/testing.py,sha256=OsHT-2B70abWH3ulY9IbhLchXIeyj3L-cfcDa88wv5E,10281
flask/typing.py,sha256=zVqhz53KklncAv-WxbpxGZfaRGOqeWAsLdP1tTMaCuE,1684
flask/views.py,sha256=F2PpWPloe4x0906IUjnPcsOqg5YvmQIfk07_lFeAD4s,5865
flask/wrappers.py,sha256=VndbHPRBSUUOejmd2Y3ydkoCVUtsS2OJIdJEVIkBVD8,5604

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.36.2)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1,3 @@
[console_scripts]
flask = flask.cli:main

View File

@ -0,0 +1 @@
flask

View File

@ -0,0 +1,28 @@
Copyright 2010 WTForms
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,53 @@
Metadata-Version: 2.1
Name: Flask-WTF
Version: 1.0.0
Summary: Form rendering, validation, and CSRF protection for Flask with WTForms.
Home-page: https://github.com/wtforms/flask-wtf/
Author: Dan Jacob
Author-email: danjac354@gmail.com
Maintainer: Hsiaoming Yang
Maintainer-email: me@lepture.com
License: BSD-3-Clause
Project-URL: Documentation, https://flask-wtf.readthedocs.io/
Project-URL: Changes, https://flask-wtf.readthedocs.io/changes/
Project-URL: Source Code, https://github.com/wtforms/flask-wtf/
Project-URL: Issue Tracker, https://github.com/wtforms/flask-wtf/issues/
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
License-File: LICENSE.rst
Requires-Dist: Flask
Requires-Dist: WTForms
Requires-Dist: itsdangerous
Provides-Extra: email
Requires-Dist: email-validator ; extra == 'email'
Flask-WTF
=========
Simple integration of Flask and WTForms, including CSRF, file upload,
and reCAPTCHA.
Links
-----
- Documentation: https://flask-wtf.readthedocs.io/
- Changes: https://flask-wtf.readthedocs.io/changes/
- PyPI Releases: https://pypi.org/project/Flask-WTF/
- Source Code: https://github.com/wtforms/flask-wtf/
- Issue Tracker: https://github.com/wtforms/flask-wtf/issues/
- Chat: https://discord.gg/pallets

View File

@ -0,0 +1,27 @@
Flask_WTF-1.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Flask_WTF-1.0.0.dist-info/LICENSE.rst,sha256=1fGQNkUVeMs27u8EyZ6_fXyi5w3PBDY2UZvEIOFafGI,1475
Flask_WTF-1.0.0.dist-info/METADATA,sha256=lVJjtO5hdgSIpqfueFAzPLji-_HDQl0vTLUQlVjo0i4,1888
Flask_WTF-1.0.0.dist-info/RECORD,,
Flask_WTF-1.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
Flask_WTF-1.0.0.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
Flask_WTF-1.0.0.dist-info/top_level.txt,sha256=zK3flQPSjYTkAMjB0V6Jhu3jyotC0biL1mMhzitYoog,10
flask_wtf/__init__.py,sha256=elEk7eARDzkJltvX-mrpvTlxv1ova6GT3AO4Qv8yQpA,214
flask_wtf/__pycache__/__init__.cpython-311.pyc,,
flask_wtf/__pycache__/_compat.cpython-311.pyc,,
flask_wtf/__pycache__/csrf.cpython-311.pyc,,
flask_wtf/__pycache__/file.cpython-311.pyc,,
flask_wtf/__pycache__/form.cpython-311.pyc,,
flask_wtf/__pycache__/i18n.cpython-311.pyc,,
flask_wtf/_compat.py,sha256=N3sqC9yzFWY-3MZ7QazX1sidvkO3d5yy4NR6lkp0s94,248
flask_wtf/csrf.py,sha256=Z407bCLwNpqjmdh6vK162hG1dHxdrZ2kly4n-Hrbyhs,10156
flask_wtf/file.py,sha256=SKm-Tjk9mYrP94cMnIdEOab1vvQEjfKZ1PwPzXNhH6o,3644
flask_wtf/form.py,sha256=Rj0vzTVuwmgIOE_0JA8pZgasVE2XHUDiyTqGKcwjdQc,4016
flask_wtf/i18n.py,sha256=T7UnjCYZ711-p6omfOHNtv9DmEKYxyXwmCD6Xk6Ydjg,1237
flask_wtf/recaptcha/__init__.py,sha256=m4eNGoU3Q0Wnt_wP8VvOlA0mwWuoMtAcK9pYT7sPFp8,106
flask_wtf/recaptcha/__pycache__/__init__.cpython-311.pyc,,
flask_wtf/recaptcha/__pycache__/fields.cpython-311.pyc,,
flask_wtf/recaptcha/__pycache__/validators.cpython-311.pyc,,
flask_wtf/recaptcha/__pycache__/widgets.cpython-311.pyc,,
flask_wtf/recaptcha/fields.py,sha256=M1-RFuUKOsJAzsLm3xaaxuhX2bB9oRqS-HVSN-NpkmI,433
flask_wtf/recaptcha/validators.py,sha256=vzsFNukVo0KiilGLyqscHh3UAWPrC5IIh6d5UdWQ6TU,2434
flask_wtf/recaptcha/widgets.py,sha256=2ty1F_0Tp7dNCWg_jtlGWysq_-c-iSRoIKHjfFYRti4,1544

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.37.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1 @@
flask_wtf

View File

@ -0,0 +1,59 @@
GitPython was originally written by Michael Trier.
GitPython 0.2 was partially (re)written by Sebastian Thiel, based on 0.1.6 and git-dulwich.
Contributors are:
-Michael Trier <mtrier _at_ gmail.com>
-Alan Briolat
-Florian Apolloner <florian _at_ apolloner.eu>
-David Aguilar <davvid _at_ gmail.com>
-Jelmer Vernooij <jelmer _at_ samba.org>
-Steve Frécinaux <code _at_ istique.net>
-Kai Lautaportti <kai _at_ lautaportti.fi>
-Paul Sowden <paul _at_ idontsmoke.co.uk>
-Sebastian Thiel <byronimo _at_ gmail.com>
-Jonathan Chu <jonathan.chu _at_ me.com>
-Vincent Driessen <me _at_ nvie.com>
-Phil Elson <pelson _dot_ pub _at_ gmail.com>
-Bernard `Guyzmo` Pratz <guyzmo+gitpython+pub@m0g.net>
-Timothy B. Hartman <tbhartman _at_ gmail.com>
-Konstantin Popov <konstantin.popov.89 _at_ yandex.ru>
-Peter Jones <pjones _at_ redhat.com>
-Anson Mansfield <anson.mansfield _at_ gmail.com>
-Ken Odegard <ken.odegard _at_ gmail.com>
-Alexis Horgix Chotard
-Piotr Babij <piotr.babij _at_ gmail.com>
-Mikuláš Poul <mikulaspoul _at_ gmail.com>
-Charles Bouchard-Légaré <cblegare.atl _at_ ntis.ca>
-Yaroslav Halchenko <debian _at_ onerussian.com>
-Tim Swast <swast _at_ google.com>
-William Luc Ritchie
-David Host <hostdm _at_ outlook.com>
-A. Jesse Jiryu Davis <jesse _at_ emptysquare.net>
-Steven Whitman <ninloot _at_ gmail.com>
-Stefan Stancu <stefan.stancu _at_ gmail.com>
-César Izurieta <cesar _at_ caih.org>
-Arthur Milchior <arthur _at_ milchior.fr>
-Anil Khatri <anil.soccer.khatri _at_ gmail.com>
-JJ Graham <thetwoj _at_ gmail.com>
-Ben Thayer <ben _at_ benthayer.com>
-Dries Kennes <admin _at_ dries007.net>
-Pratik Anurag <panurag247365 _at_ gmail.com>
-Harmon <harmon.public _at_ gmail.com>
-Liam Beguin <liambeguin _at_ gmail.com>
-Ram Rachum <ram _at_ rachum.com>
-Alba Mendez <me _at_ alba.sh>
-Robert Westman <robert _at_ byteflux.io>
-Hugo van Kemenade
-Hiroki Tokunaga <tokusan441 _at_ gmail.com>
-Julien Mauroy <pro.julien.mauroy _at_ gmail.com>
-Patrick Gerard
-Luke Twist <itsluketwist@gmail.com>
-Joseph Hale <me _at_ jhale.dev>
-Santos Gallegos <stsewd _at_ proton.me>
-Wenhan Zhu <wzhu.cosmos _at_ gmail.com>
-Eliah Kagan <eliah.kagan _at_ gmail.com>
-Ethan Lin <et.repositories _at_ gmail.com>
-Jonas Scharpf <jonas.scharpf _at_ checkmk.com>
Portions derived from other open source works and are clearly marked.

View File

@ -0,0 +1,29 @@
Copyright (C) 2008, 2009 Michael Trier and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the GitPython project nor the names of
its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,295 @@
Metadata-Version: 2.1
Name: GitPython
Version: 3.1.44
Summary: GitPython is a Python library used to interact with Git repositories
Home-page: https://github.com/gitpython-developers/GitPython
Author: Sebastian Thiel, Michael Trier
Author-email: byronimo@gmail.com, mtrier@gmail.com
License: BSD-3-Clause
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Typing :: Typed
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: AUTHORS
Requires-Dist: gitdb<5,>=4.0.1
Requires-Dist: typing-extensions>=3.7.4.3; python_version < "3.8"
Provides-Extra: test
Requires-Dist: coverage[toml]; extra == "test"
Requires-Dist: ddt!=1.4.3,>=1.1.1; extra == "test"
Requires-Dist: mock; python_version < "3.8" and extra == "test"
Requires-Dist: mypy; extra == "test"
Requires-Dist: pre-commit; extra == "test"
Requires-Dist: pytest>=7.3.1; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Requires-Dist: pytest-instafail; extra == "test"
Requires-Dist: pytest-mock; extra == "test"
Requires-Dist: pytest-sugar; extra == "test"
Requires-Dist: typing-extensions; python_version < "3.11" and extra == "test"
Provides-Extra: doc
Requires-Dist: sphinx<7.2,>=7.1.2; extra == "doc"
Requires-Dist: sphinx_rtd_theme; extra == "doc"
Requires-Dist: sphinx-autodoc-typehints; extra == "doc"
![Python package](https://github.com/gitpython-developers/GitPython/workflows/Python%20package/badge.svg)
[![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable)
[![Packaging status](https://repology.org/badge/tiny-repos/python:gitpython.svg)](https://repology.org/metapackage/python:gitpython/versions)
## [Gitoxide](https://github.com/Byron/gitoxide): A peek into the future…
I started working on GitPython in 2009, back in the days when Python was 'my thing' and I had great plans with it.
Of course, back in the days, I didn't really know what I was doing and this shows in many places. Somewhat similar to
Python this happens to be 'good enough', but at the same time is deeply flawed and broken beyond repair.
By now, GitPython is widely used and I am sure there is a good reason for that, it's something to be proud of and happy about.
The community is maintaining the software and is keeping it relevant for which I am absolutely grateful. For the time to come I am happy to continue maintaining GitPython, remaining hopeful that one day it won't be needed anymore.
More than 15 years after my first meeting with 'git' I am still in excited about it, and am happy to finally have the tools and
probably the skills to scratch that itch of mine: implement `git` in a way that makes tool creation a piece of cake for most.
If you like the idea and want to learn more, please head over to [gitoxide](https://github.com/Byron/gitoxide), an
implementation of 'git' in [Rust](https://www.rust-lang.org).
*(Please note that `gitoxide` is not currently available for use in Python, and that Rust is required.)*
## GitPython
GitPython is a python library used to interact with git repositories, high-level like git-porcelain,
or low-level like git-plumbing.
It provides abstractions of git objects for easy access of repository data often backed by calling the `git`
command-line program.
### DEVELOPMENT STATUS
This project is in **maintenance mode**, which means that
- …there will be no feature development, unless these are contributed
- …there will be no bug fixes, unless they are relevant to the safety of users, or contributed
- …issues will be responded to with waiting times of up to a month
The project is open to contributions of all kinds, as well as new maintainers.
### REQUIREMENTS
GitPython needs the `git` executable to be installed on the system and available in your
`PATH` for most operations. If it is not in your `PATH`, you can help GitPython find it
by setting the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
- Git (1.7.x or newer)
- Python >= 3.7
The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`.
The installer takes care of installing them for you.
### INSTALL
GitPython and its required package dependencies can be installed in any of the following ways, all of which should typically be done in a [virtual environment](https://docs.python.org/3/tutorial/venv.html).
#### From PyPI
To obtain and install a copy [from PyPI](https://pypi.org/project/GitPython/), run:
```sh
pip install GitPython
```
(A distribution package can also be downloaded for manual installation at [the PyPI page](https://pypi.org/project/GitPython/).)
#### From downloaded source code
If you have downloaded the source code, run this from inside the unpacked `GitPython` directory:
```sh
pip install .
```
#### By cloning the source code repository
To clone the [the GitHub repository](https://github.com/gitpython-developers/GitPython) from source to work on the code, you can do it like so:
```sh
git clone https://github.com/gitpython-developers/GitPython
cd GitPython
./init-tests-after-clone.sh
```
On Windows, `./init-tests-after-clone.sh` can be run in a Git Bash shell.
If you are cloning [your own fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks), then replace the above `git clone` command with one that gives the URL of your fork. Or use this [`gh`](https://cli.github.com/) command (assuming you have `gh` and your fork is called `GitPython`):
```sh
gh repo clone GitPython
```
Having cloned the repo, create and activate your [virtual environment](https://docs.python.org/3/tutorial/venv.html).
Then make an [editable install](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs):
```sh
pip install -e ".[test]"
```
In the less common case that you do not want to install test dependencies, `pip install -e .` can be used instead.
#### With editable *dependencies* (not preferred, and rarely needed)
In rare cases, you may want to work on GitPython and one or both of its [gitdb](https://github.com/gitpython-developers/gitdb) and [smmap](https://github.com/gitpython-developers/smmap) dependencies at the same time, with changes in your local working copy of gitdb or smmap immediately reflected in the behavior of your local working copy of GitPython. This can be done by making editable installations of those dependencies in the same virtual environment where you install GitPython.
If you want to do that *and* you want the versions in GitPython's git submodules to be used, then pass `-e git/ext/gitdb` and/or `-e git/ext/gitdb/gitdb/ext/smmap` to `pip install`. This can be done in any order, and in separate `pip install` commands or the same one, so long as `-e` appears before *each* path. For example, you can install GitPython, gitdb, and smmap editably in the currently active virtual environment this way:
```sh
pip install -e ".[test]" -e git/ext/gitdb -e git/ext/gitdb/gitdb/ext/smmap
```
The submodules must have been cloned for that to work, but that will already be the case if you have run `./init-tests-after-clone.sh`. You can use `pip list` to check which packages are installed editably and which are installed normally.
To reiterate, this approach should only rarely be used. For most development it is preferable to allow the gitdb and smmap dependencices to be retrieved automatically from PyPI in their latest stable packaged versions.
### Limitations
#### Leakage of System Resources
GitPython is not suited for long-running processes (like daemons) as it tends to
leak system resources. It was written in a time where destructors (as implemented
in the `__del__` method) still ran deterministically.
In case you still want to use it in such a context, you will want to search the
codebase for `__del__` implementations and call these yourself when you see fit.
Another way assure proper cleanup of resources is to factor out GitPython into a
separate process which can be dropped periodically.
#### Windows support
See [Issue #525](https://github.com/gitpython-developers/GitPython/issues/525).
### RUNNING TESTS
_Important_: Right after cloning this repository, please be sure to have executed
the `./init-tests-after-clone.sh` script in the repository root. Otherwise
you will encounter test failures.
#### Install test dependencies
Ensure testing libraries are installed. This is taken care of already if you installed with:
```sh
pip install -e ".[test]"
```
If you had installed with a command like `pip install -e .` instead, you can still run
the above command to add the testing dependencies.
#### Test commands
To test, run:
```sh
pytest
```
To lint, and apply some linting fixes as well as automatic code formatting, run:
```sh
pre-commit run --all-files
```
This includes the linting and autoformatting done by Ruff, as well as some other checks.
To typecheck, run:
```sh
mypy
```
#### CI (and tox)
Style and formatting checks, and running tests on all the different supported Python versions, will be performed:
- Upon submitting a pull request.
- On each push, *if* you have a fork with GitHub Actions enabled.
- Locally, if you run [`tox`](https://tox.wiki/) (this skips any Python versions you don't have installed).
#### Configuration files
Specific tools are all configured in the `./pyproject.toml` file:
- `pytest` (test runner)
- `coverage.py` (code coverage)
- `ruff` (linter and formatter)
- `mypy` (type checker)
Orchestration tools:
- Configuration for `pre-commit` is in the `./.pre-commit-config.yaml` file.
- Configuration for `tox` is in `./tox.ini`.
- Configuration for GitHub Actions (CI) is in files inside `./.github/workflows/`.
### Contributions
Please have a look at the [contributions file][contributing].
### INFRASTRUCTURE
- [User Documentation](http://gitpython.readthedocs.org)
- [Questions and Answers](http://stackexchange.com/filters/167317/gitpython)
- Please post on Stack Overflow and use the `gitpython` tag
- [Issue Tracker](https://github.com/gitpython-developers/GitPython/issues)
- Post reproducible bugs and feature requests as a new issue.
Please be sure to provide the following information if posting bugs:
- GitPython version (e.g. `import git; git.__version__`)
- Python version (e.g. `python --version`)
- The encountered stack-trace, if applicable
- Enough information to allow reproducing the issue
### How to make a new release
1. Update/verify the **version** in the `VERSION` file.
2. Update/verify that the `doc/source/changes.rst` changelog file was updated. It should include a link to the forthcoming release page: `https://github.com/gitpython-developers/GitPython/releases/tag/<version>`
3. Commit everything.
4. Run `git tag -s <version>` to tag the version in Git.
5. _Optionally_ create and activate a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment). (Then the next step can install `build` and `twine`.)
6. Run `make release`.
7. Go to [GitHub Releases](https://github.com/gitpython-developers/GitPython/releases) and publish a new one with the recently pushed tag. Generate the changelog.
### Projects using GitPython
- [PyDriller](https://github.com/ishepard/pydriller)
- [Kivy Designer](https://github.com/kivy/kivy-designer)
- [Prowl](https://github.com/nettitude/Prowl)
- [Python Taint](https://github.com/python-security/pyt)
- [Buster](https://github.com/axitkhurana/buster)
- [git-ftp](https://github.com/ezyang/git-ftp)
- [Git-Pandas](https://github.com/wdm0006/git-pandas)
- [PyGitUp](https://github.com/msiemens/PyGitUp)
- [PyJFuzz](https://github.com/mseclab/PyJFuzz)
- [Loki](https://github.com/Neo23x0/Loki)
- [Omniwallet](https://github.com/OmniLayer/omniwallet)
- [GitViper](https://github.com/BeayemX/GitViper)
- [Git Gud](https://github.com/bthayer2365/git-gud)
### LICENSE
[3-Clause BSD License](https://opensource.org/license/bsd-3-clause/), also known as the New BSD License. See the [LICENSE file][license].
One file exclusively used for fuzz testing is subject to [a separate license, detailed here](./fuzzing/README.md#license).
This file is not included in the wheel or sdist packages published by the maintainers of GitPython.
[contributing]: https://github.com/gitpython-developers/GitPython/blob/main/CONTRIBUTING.md
[license]: https://github.com/gitpython-developers/GitPython/blob/main/LICENSE

View File

@ -0,0 +1,83 @@
GitPython-3.1.44.dist-info/AUTHORS,sha256=tZ9LuyBks2V2HKTPK7kCmtd9Guu_LyU1oZHvU0NiAok,2334
GitPython-3.1.44.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
GitPython-3.1.44.dist-info/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
GitPython-3.1.44.dist-info/METADATA,sha256=0O_Fr2Y7A-DlPYhlbSxGjblBC2mWkw3USNUhyL80Ip8,13245
GitPython-3.1.44.dist-info/RECORD,,
GitPython-3.1.44.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
GitPython-3.1.44.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
GitPython-3.1.44.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
git/__init__.py,sha256=nkQImgv-bWdiZOFDjzN-gbt93FoRHD0nY6_t9LQxy4Y,8899
git/__pycache__/__init__.cpython-311.pyc,,
git/__pycache__/cmd.cpython-311.pyc,,
git/__pycache__/compat.cpython-311.pyc,,
git/__pycache__/config.cpython-311.pyc,,
git/__pycache__/db.cpython-311.pyc,,
git/__pycache__/diff.cpython-311.pyc,,
git/__pycache__/exc.cpython-311.pyc,,
git/__pycache__/remote.cpython-311.pyc,,
git/__pycache__/types.cpython-311.pyc,,
git/__pycache__/util.cpython-311.pyc,,
git/cmd.py,sha256=QwiaBy0mFbi9xjRKhRgUVK-_-K6xVdFqh9l0cxPqPSc,67724
git/compat.py,sha256=y1E6y6O2q5r8clSlr8ZNmuIWG9nmHuehQEsVsmBffs8,4526
git/config.py,sha256=vTUlK6d8ORqFqjOv4Vbq_Hm-5mp-jOAt1dkq0IdzJ3U,34933
git/db.py,sha256=vIW9uWSbqu99zbuU2ZDmOhVOv1UPTmxrnqiCtRHCfjE,2368
git/diff.py,sha256=wmpMCIdMiVOqreGVPOGYyO4gFboGOAicyrvvI7PPjEg,27095
git/exc.py,sha256=Gc7g1pHpn8OmTse30NHmJVsBJ2CYH8LxaR8y8UA3lIM,7119
git/index/__init__.py,sha256=i-Nqb8Lufp9aFbmxpQBORmmQnjEVVM1Pn58fsQkyGgQ,406
git/index/__pycache__/__init__.cpython-311.pyc,,
git/index/__pycache__/base.cpython-311.pyc,,
git/index/__pycache__/fun.cpython-311.pyc,,
git/index/__pycache__/typ.cpython-311.pyc,,
git/index/__pycache__/util.cpython-311.pyc,,
git/index/base.py,sha256=nDD7XVLNbgBKpJMrrTVyHBy6NVLWgDkk7oUw6ZOegPc,60808
git/index/fun.py,sha256=37cA3DBC9vpAnSVu5TGA072SnoF5XZOkOukExwlejHs,16736
git/index/typ.py,sha256=uuKNwitUw83FhVaLSwo4pY7PHDQudtZTLJrLGym4jcI,6570
git/index/util.py,sha256=fULi7GPG-MvprKrRCD5c15GNdzku_1E38We0d97WB3A,3659
git/objects/__init__.py,sha256=O6ZL_olX7e5-8iIbKviRPkVSJxN37WA-EC0q9d48U5Y,637
git/objects/__pycache__/__init__.cpython-311.pyc,,
git/objects/__pycache__/base.cpython-311.pyc,,
git/objects/__pycache__/blob.cpython-311.pyc,,
git/objects/__pycache__/commit.cpython-311.pyc,,
git/objects/__pycache__/fun.cpython-311.pyc,,
git/objects/__pycache__/tag.cpython-311.pyc,,
git/objects/__pycache__/tree.cpython-311.pyc,,
git/objects/__pycache__/util.cpython-311.pyc,,
git/objects/base.py,sha256=0dqNkSRVH0mk0-7ZKIkGBK7iNYrzLTVxwQFUd6CagsE,10277
git/objects/blob.py,sha256=zwwq0KfOMYeP5J2tW5CQatoLyeqFRlfkxP1Vwx1h07s,1215
git/objects/commit.py,sha256=GH1_83C9t7RGTukwozTHDgvxYQPRjTHhPDkXJyBbJyo,30553
git/objects/fun.py,sha256=B4jCqhAjm6Hl79GK58FPzW1H9K6Wc7Tx0rssyWmAcEE,8935
git/objects/submodule/__init__.py,sha256=6xySp767LVz3UylWgUalntS_nGXRuVzXxDuFAv_Wc2c,303
git/objects/submodule/__pycache__/__init__.cpython-311.pyc,,
git/objects/submodule/__pycache__/base.cpython-311.pyc,,
git/objects/submodule/__pycache__/root.cpython-311.pyc,,
git/objects/submodule/__pycache__/util.cpython-311.pyc,,
git/objects/submodule/base.py,sha256=MQ-2xV8JznGwy2hLQv1aeQNgAkhBhgc5tdtClFL3DmE,63901
git/objects/submodule/root.py,sha256=5eTtYNHasqdPq6q0oDCPr7IaO6uAHL3b4DxMoiO2LhE,20246
git/objects/submodule/util.py,sha256=sQqAYaiSJdFkZa9NlAuK_wTsMNiS-kkQnQjvIoJtc_o,3509
git/objects/tag.py,sha256=jAGESnpmTEv-dLakPzheT5ILZFFArcItnXYqfxfDrgc,4441
git/objects/tree.py,sha256=jJH888SHiP4dGzE-ra1yenQOyya_0C_MkHr06c1gHpM,13849
git/objects/util.py,sha256=Nlza4zLgdPmr_Yasyvvs6c1rKtW_wMxI6wDmQpQ3ufw,23846
git/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
git/refs/__init__.py,sha256=DWlJNnsx-4jM_E-VycbP-FZUdn6iWhjnH_uZ_pZXBro,509
git/refs/__pycache__/__init__.cpython-311.pyc,,
git/refs/__pycache__/head.cpython-311.pyc,,
git/refs/__pycache__/log.cpython-311.pyc,,
git/refs/__pycache__/reference.cpython-311.pyc,,
git/refs/__pycache__/remote.cpython-311.pyc,,
git/refs/__pycache__/symbolic.cpython-311.pyc,,
git/refs/__pycache__/tag.cpython-311.pyc,,
git/refs/head.py,sha256=SGa3N301HfAi79X6UR5Mcg7mO9TnCH3Bk549kHlJVaQ,10513
git/refs/log.py,sha256=kXiuAgTo1DIuM_BfbDUk9gQ0YO-mutIMVdHv1_ES90o,12493
git/refs/reference.py,sha256=l6mhF4YLSEwtjz6b9PpOQH-fkng7EYWMaJhkjn-2jXA,5630
git/refs/remote.py,sha256=WwqV9T7BbYf3F_WZNUQivu9xktIIKGklCjDpwQrhD-A,2806
git/refs/symbolic.py,sha256=c8zOwaqzcg-J-rGrpuWdvh8zwMvSUqAHghd4vJoYG_s,34552
git/refs/tag.py,sha256=kgzV2vhpL4FD2TqHb0BJuMRAHgAvJF-TcoyWlaB-djQ,5010
git/remote.py,sha256=pYn9dAlz-QwvNMWXD1M57pMPQitthOM86qTRK_cpTqU,46786
git/repo/__init__.py,sha256=CILSVH36fX_WxVFSjD9o1WF5LgsNedPiJvSngKZqfVU,210
git/repo/__pycache__/__init__.cpython-311.pyc,,
git/repo/__pycache__/base.cpython-311.pyc,,
git/repo/__pycache__/fun.cpython-311.pyc,,
git/repo/base.py,sha256=0GU6nKNdT8SYjDI5Y5DeZ1zCEX3tHeq1VW2MSpne05g,59891
git/repo/fun.py,sha256=HSGC0-rqeKKx9fDg7JyQyMZgIwUWn-FnSZR_gRGpG-E,13573
git/types.py,sha256=MQzIDEOnoueXGsAJF_0MgUc_osH7Eu0Sw3DQofYzCVE,10272
git/util.py,sha256=2uAv34zZ_827-zJ3-D5ACrVH-4Q4EO_KLUTH23zi2AI,43770

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: setuptools (75.6.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1 @@
pip

View File

@ -0,0 +1,28 @@
Copyright 2007 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,112 @@
Metadata-Version: 2.1
Name: Jinja2
Version: 3.0.1
Summary: A very fast and expressive template engine.
Home-page: https://palletsprojects.com/p/jinja/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://jinja.palletsprojects.com/
Project-URL: Changes, https://jinja.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/jinja/
Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
Requires-Dist: MarkupSafe (>=2.0)
Provides-Extra: i18n
Requires-Dist: Babel (>=2.7) ; extra == 'i18n'
Jinja
=====
Jinja is a fast, expressive, extensible templating engine. Special
placeholders in the template allow writing code similar to Python
syntax. Then the template is passed data to render the final document.
It includes:
- Template inheritance and inclusion.
- Define and import macros within templates.
- HTML templates can use autoescaping to prevent XSS from untrusted
user input.
- A sandboxed environment can safely render untrusted templates.
- AsyncIO support for generating templates and calling async
functions.
- I18N support with Babel.
- Templates are compiled to optimized Python code just-in-time and
cached, or can be compiled ahead-of-time.
- Exceptions point to the correct line in templates to make debugging
easier.
- Extensible filters, tests, functions, and even syntax.
Jinja's philosophy is that while application logic belongs in Python if
possible, it shouldn't make the template designer's job difficult by
restricting functionality too much.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install -U Jinja2
.. _pip: https://pip.pypa.io/en/stable/quickstart/
In A Nutshell
-------------
.. code-block:: jinja
{% extends "base.html" %}
{% block title %}Members{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Donate
------
The Pallets organization develops and supports Jinja and other popular
packages. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://jinja.palletsprojects.com/
- Changes: https://jinja.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Jinja2/
- Source Code: https://github.com/pallets/jinja/
- Issue Tracker: https://github.com/pallets/jinja/issues/
- Website: https://palletsprojects.com/p/jinja/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

View File

@ -0,0 +1,59 @@
Jinja2-3.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Jinja2-3.0.1.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
Jinja2-3.0.1.dist-info/METADATA,sha256=k6STiOONbGESP2rEKmjhznuG10vm9sNCHCUQL9AQFM4,3508
Jinja2-3.0.1.dist-info/RECORD,,
Jinja2-3.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
Jinja2-3.0.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
Jinja2-3.0.1.dist-info/entry_points.txt,sha256=Qy_DkVo6Xj_zzOtmErrATe8lHZhOqdjpt3e4JJAGyi8,61
Jinja2-3.0.1.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
jinja2/__init__.py,sha256=fd8jaCRsCATgC7ahuUTD8CyfQoc4aRfALEIny4mwfog,2205
jinja2/__pycache__/__init__.cpython-311.pyc,,
jinja2/__pycache__/_identifier.cpython-311.pyc,,
jinja2/__pycache__/async_utils.cpython-311.pyc,,
jinja2/__pycache__/bccache.cpython-311.pyc,,
jinja2/__pycache__/compiler.cpython-311.pyc,,
jinja2/__pycache__/constants.cpython-311.pyc,,
jinja2/__pycache__/debug.cpython-311.pyc,,
jinja2/__pycache__/defaults.cpython-311.pyc,,
jinja2/__pycache__/environment.cpython-311.pyc,,
jinja2/__pycache__/exceptions.cpython-311.pyc,,
jinja2/__pycache__/ext.cpython-311.pyc,,
jinja2/__pycache__/filters.cpython-311.pyc,,
jinja2/__pycache__/idtracking.cpython-311.pyc,,
jinja2/__pycache__/lexer.cpython-311.pyc,,
jinja2/__pycache__/loaders.cpython-311.pyc,,
jinja2/__pycache__/meta.cpython-311.pyc,,
jinja2/__pycache__/nativetypes.cpython-311.pyc,,
jinja2/__pycache__/nodes.cpython-311.pyc,,
jinja2/__pycache__/optimizer.cpython-311.pyc,,
jinja2/__pycache__/parser.cpython-311.pyc,,
jinja2/__pycache__/runtime.cpython-311.pyc,,
jinja2/__pycache__/sandbox.cpython-311.pyc,,
jinja2/__pycache__/tests.cpython-311.pyc,,
jinja2/__pycache__/utils.cpython-311.pyc,,
jinja2/__pycache__/visitor.cpython-311.pyc,,
jinja2/_identifier.py,sha256=EdgGJKi7O1yvr4yFlvqPNEqV6M1qHyQr8Gt8GmVTKVM,1775
jinja2/async_utils.py,sha256=bY2nCUfBA_4FSnNUsIsJgljBq3hACr6fzLi7LiyMTn8,1751
jinja2/bccache.py,sha256=smAvSDgDSvXdvJzCN_9s0XfkVpQEu8be-QwgeMlrwiM,12677
jinja2/compiler.py,sha256=qq0Fo9EpDAEwHPLAs3sAP7dindUvDrFrbx4AcB8xV5M,72046
jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433
jinja2/debug.py,sha256=uBmrsiwjYH5l14R9STn5mydOOyriBYol5lDGvEqAb3A,9238
jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267
jinja2/environment.py,sha256=T6U4be9mY1CUXXin_EQFwpvpFqCiryweGqzXGRYIoSA,61573
jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071
jinja2/ext.py,sha256=44SjDjeYkkxQTpmC2BetOTxEFMgQ42p2dfSwXmPFcSo,32122
jinja2/filters.py,sha256=LslRsJd0JVFBHtdfU_WraM1eQitotciwawiW-seR42U,52577
jinja2/idtracking.py,sha256=KdFVohVNK-baOwt_INPMco19D7AfLDEN8i3_JoiYnGQ,10713
jinja2/lexer.py,sha256=D5qOKB3KnRqK9gPAZFQvRguomYsQok5-14TKiWTN8Jw,29923
jinja2/loaders.py,sha256=ePpWB0xDrILgLVqNFcxqqSbPizsI0T-JlkNEUFqq9fo,22350
jinja2/meta.py,sha256=GNPEvifmSaU3CMxlbheBOZjeZ277HThOPUTf1RkppKQ,4396
jinja2/nativetypes.py,sha256=62hvvsAxAj0YaxylOHoREYVogJ5JqOlJISgGY3OKd_o,3675
jinja2/nodes.py,sha256=LHF97fu6GW4r2Z9UaOX92MOT1wZpdS9Nx4N-5Fp5ti8,34509
jinja2/optimizer.py,sha256=tHkMwXxfZkbfA1KmLcqmBMSaz7RLIvvItrJcPoXTyD8,1650
jinja2/parser.py,sha256=kHnU8v92GwMYkfr0MVakWv8UlSf_kJPx8LUsgQMof70,39767
jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
jinja2/runtime.py,sha256=bSWdawLjReKpKHhF3-96OIuWYpUy1yxFJCN3jBYyoXc,35013
jinja2/sandbox.py,sha256=-8zxR6TO9kUkciAVFsIKu8Oq-C7PTeYEdZ5TtA55-gw,14600
jinja2/tests.py,sha256=Am5Z6Lmfr2XaH_npIfJJ8MdXtWsbLjMULZJulTAj30E,5905
jinja2/utils.py,sha256=0wGkxDbxlW10y0ac4-kEiy1Bn0AsWXqz8uomK9Ugy1Q,26961
jinja2/visitor.py,sha256=ZmeLuTj66ic35-uFH-1m0EKXiw4ObDDb_WuE6h5vPFg,3572

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.36.2)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1,3 @@
[babel.extractors]
jinja2 = jinja2.ext:babel_extract [i18n]

View File

@ -0,0 +1 @@
jinja2

View File

@ -0,0 +1,28 @@
Copyright 2007 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,128 @@
Metadata-Version: 2.1
Name: Werkzeug
Version: 2.0.1
Summary: The comprehensive WSGI web application library.
Home-page: https://palletsprojects.com/p/werkzeug/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://werkzeug.palletsprojects.com/
Project-URL: Changes, https://werkzeug.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/werkzeug/
Project-URL: Issue Tracker, https://github.com/pallets/werkzeug/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
Requires-Dist: dataclasses ; python_version < "3.7"
Provides-Extra: watchdog
Requires-Dist: watchdog ; extra == 'watchdog'
Werkzeug
========
*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff")
Werkzeug is a comprehensive `WSGI`_ web application library. It began as
a simple collection of various utilities for WSGI applications and has
become one of the most advanced WSGI utility libraries.
It includes:
- An interactive debugger that allows inspecting stack traces and
source code in the browser with an interactive interpreter for any
frame in the stack.
- A full-featured request object with objects to interact with
headers, query args, form data, files, and cookies.
- A response object that can wrap other WSGI applications and handle
streaming data.
- A routing system for matching URLs to endpoints and generating URLs
for endpoints, with an extensible system for capturing variables
from URLs.
- HTTP utilities to handle entity tags, cache control, dates, user
agents, cookies, files, and more.
- A threaded WSGI server for use while developing applications
locally.
- A test client for simulating HTTP requests during testing without
requiring running a server.
Werkzeug doesn't enforce any dependencies. It is up to the developer to
choose a template engine, database adapter, and even how to handle
requests. It can be used to build all sorts of end user applications
such as blogs, wikis, or bulletin boards.
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
providing more structure and patterns for defining powerful
applications.
.. _WSGI: https://wsgi.readthedocs.io/en/latest/
.. _Flask: https://www.palletsprojects.com/p/flask/
Installing
----------
Install and update using `pip`_:
.. code-block:: text
pip install -U Werkzeug
.. _pip: https://pip.pypa.io/en/stable/quickstart/
A Simple Example
----------------
.. code-block:: python
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello, World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
Donate
------
The Pallets organization develops and supports Werkzeug and other
popular packages. In order to grow the community of contributors and
users, and allow the maintainers to devote more time to the projects,
`please donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://werkzeug.palletsprojects.com/
- Changes: https://werkzeug.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Werkzeug/
- Source Code: https://github.com/pallets/werkzeug/
- Issue Tracker: https://github.com/pallets/werkzeug/issues/
- Website: https://palletsprojects.com/p/werkzeug/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

View File

@ -0,0 +1,112 @@
Werkzeug-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Werkzeug-2.0.1.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
Werkzeug-2.0.1.dist-info/METADATA,sha256=8-W33EMnGqnCCi-d8Dv63IQQuyELRIsXhwOJNXbNgL0,4421
Werkzeug-2.0.1.dist-info/RECORD,,
Werkzeug-2.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
Werkzeug-2.0.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
Werkzeug-2.0.1.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9
werkzeug/__init__.py,sha256=_CCsfdeqNllFNRJx8cvqYrwBsQQQXJaMmnk2sAZnDng,188
werkzeug/__pycache__/__init__.cpython-311.pyc,,
werkzeug/__pycache__/_internal.cpython-311.pyc,,
werkzeug/__pycache__/_reloader.cpython-311.pyc,,
werkzeug/__pycache__/datastructures.cpython-311.pyc,,
werkzeug/__pycache__/exceptions.cpython-311.pyc,,
werkzeug/__pycache__/filesystem.cpython-311.pyc,,
werkzeug/__pycache__/formparser.cpython-311.pyc,,
werkzeug/__pycache__/http.cpython-311.pyc,,
werkzeug/__pycache__/local.cpython-311.pyc,,
werkzeug/__pycache__/routing.cpython-311.pyc,,
werkzeug/__pycache__/security.cpython-311.pyc,,
werkzeug/__pycache__/serving.cpython-311.pyc,,
werkzeug/__pycache__/test.cpython-311.pyc,,
werkzeug/__pycache__/testapp.cpython-311.pyc,,
werkzeug/__pycache__/urls.cpython-311.pyc,,
werkzeug/__pycache__/user_agent.cpython-311.pyc,,
werkzeug/__pycache__/useragents.cpython-311.pyc,,
werkzeug/__pycache__/utils.cpython-311.pyc,,
werkzeug/__pycache__/wsgi.cpython-311.pyc,,
werkzeug/_internal.py,sha256=_QKkvdaG4pDFwK68c0EpPzYJGe9Y7toRAT1cBbC-CxU,18572
werkzeug/_reloader.py,sha256=B1hEfgsUOz2IginBQM5Zak_eaIF7gr3GS5-0x2OHvAE,13950
werkzeug/datastructures.py,sha256=KahVPSLOapbNbKh1ppr9K8_DgWJv1EGgA9DhTEGMHcg,97886
werkzeug/datastructures.pyi,sha256=5DTPF8P8Zvi458eK27Qcj7eNUlLM_AC0jBNkj6uQpds,33774
werkzeug/debug/__init__.py,sha256=CUFrPEYAaotHRkmjOieqd3EasXDii2JVC1HdmEzMwqM,17924
werkzeug/debug/__pycache__/__init__.cpython-311.pyc,,
werkzeug/debug/__pycache__/console.cpython-311.pyc,,
werkzeug/debug/__pycache__/repr.cpython-311.pyc,,
werkzeug/debug/__pycache__/tbtools.cpython-311.pyc,,
werkzeug/debug/console.py,sha256=E1nBMEvFkX673ShQjPtVY-byYatfX9MN-dBMjRI8a8E,5897
werkzeug/debug/repr.py,sha256=QCSHENKsChEZDCIApkVi_UNjhJ77v8BMXK1OfxO189M,9483
werkzeug/debug/shared/FONT_LICENSE,sha256=LwAVEI1oYnvXiNMT9SnCH_TaLCxCpeHziDrMg0gPkAI,4673
werkzeug/debug/shared/ICON_LICENSE.md,sha256=DhA6Y1gUl5Jwfg0NFN9Rj4VWITt8tUx0IvdGf0ux9-s,222
werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507
werkzeug/debug/shared/debugger.js,sha256=dYbUmFmb3YZb5YpWpYPOQArdrN7NPeY0ODawL7ihzDI,10524
werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191
werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200
werkzeug/debug/shared/source.png,sha256=RoGcBTE4CyCB85GBuDGTFlAnUqxwTBiIfDqW15EpnUQ,818
werkzeug/debug/shared/style.css,sha256=vyp1RnB227Fuw8LIyM5C-bBCBQN5hvZSCApY2oeJ9ik,6705
werkzeug/debug/shared/ubuntu.ttf,sha256=1eaHFyepmy4FyDvjLVzpITrGEBu_CZYY94jE0nED1c0,70220
werkzeug/debug/tbtools.py,sha256=TfReusPbM3yjm3xvOFkH45V7-5JnNqB9x1EQPnVC6Xo,19189
werkzeug/exceptions.py,sha256=CUwx0pBiNbk4f9cON17ekgKnmLi6HIVFjUmYZc2x0wM,28681
werkzeug/filesystem.py,sha256=JS2Dv2QF98WILxY4_thHl-WMcUcwluF_4igkDPaP1l4,1956
werkzeug/formparser.py,sha256=GIKfzuQ_khuBXnf3N7_LzOEruYwNc3m4bI02BgtT5jg,17385
werkzeug/http.py,sha256=oUCXFFMnkOQ-cHbUY_aiqitshcrSzNDq3fEMf1VI_yk,45141
werkzeug/local.py,sha256=WsR6H-2XOtPigpimjORbLsS3h9WI0lCdZjGI2LHDDxA,22733
werkzeug/middleware/__init__.py,sha256=qfqgdT5npwG9ses3-FXQJf3aB95JYP1zchetH_T3PUw,500
werkzeug/middleware/__pycache__/__init__.cpython-311.pyc,,
werkzeug/middleware/__pycache__/dispatcher.cpython-311.pyc,,
werkzeug/middleware/__pycache__/http_proxy.cpython-311.pyc,,
werkzeug/middleware/__pycache__/lint.cpython-311.pyc,,
werkzeug/middleware/__pycache__/profiler.cpython-311.pyc,,
werkzeug/middleware/__pycache__/proxy_fix.cpython-311.pyc,,
werkzeug/middleware/__pycache__/shared_data.cpython-311.pyc,,
werkzeug/middleware/dispatcher.py,sha256=Fh_w-KyWnTSYF-Lfv5dimQ7THSS7afPAZMmvc4zF1gg,2580
werkzeug/middleware/http_proxy.py,sha256=HE8VyhS7CR-E1O6_9b68huv8FLgGGR1DLYqkS3Xcp3Q,7558
werkzeug/middleware/lint.py,sha256=yMzMdm4xI2_N-Wv2j1yoaVI3ltHOYS6yZyA-wUv1sKw,13962
werkzeug/middleware/profiler.py,sha256=G2JieUMv4QPamtCY6ibIK7P-piPRdPybav7bm2MSFvs,4898
werkzeug/middleware/proxy_fix.py,sha256=uRgQ3dEvFV8JxUqajHYYYOPEeA_BFqaa51Yp8VW0uzA,6849
werkzeug/middleware/shared_data.py,sha256=eOCGr-i6BCexDfL7xdPRWMwPJPgp0NE2B416Gl67Q78,10959
werkzeug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
werkzeug/routing.py,sha256=FDRtvCfaZSmXnQ0cUYxowb3P0y0dxlUlMSUmerY5sb0,84147
werkzeug/sansio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
werkzeug/sansio/__pycache__/__init__.cpython-311.pyc,,
werkzeug/sansio/__pycache__/multipart.cpython-311.pyc,,
werkzeug/sansio/__pycache__/request.cpython-311.pyc,,
werkzeug/sansio/__pycache__/response.cpython-311.pyc,,
werkzeug/sansio/__pycache__/utils.cpython-311.pyc,,
werkzeug/sansio/multipart.py,sha256=bJMCNC2f5xyAaylahNViJ0JqmV4ThLRbDVGVzKwcqrQ,8751
werkzeug/sansio/request.py,sha256=aA9rABkWiG4MhYMByanst2NXkEclsq8SIxhb0LQf0e0,20228
werkzeug/sansio/response.py,sha256=HSG6t-tyPZd3awzWqr7qL9IV4HYAvDgON1c0YPU2RXw,24117
werkzeug/sansio/utils.py,sha256=V5v-UUnX8pm4RehP9Tt_NiUSOJGJGUvKjlW0eOIQldM,4164
werkzeug/security.py,sha256=gPDRuCjkjWrcqj99tBMq8_nHFZLFQjgoW5Ga5XIw9jo,8158
werkzeug/serving.py,sha256=_RG2dCclOQcdjJ2NE8tzCRybGePlwcs8kTypiWRP2gY,38030
werkzeug/test.py,sha256=EJXJy-b_JriHrlfs5VNCkwbki8Kn_xUDkOYOCx_6Q7Q,48096
werkzeug/testapp.py,sha256=f48prWSGJhbSrvYb8e1fnAah4BkrLb0enHSdChgsjBY,9471
werkzeug/urls.py,sha256=3o_aUcr5Ou13XihSU6VvX6RHMhoWkKpXuCCia9SSAb8,41021
werkzeug/user_agent.py,sha256=WclZhpvgLurMF45hsioSbS75H1Zb4iMQGKN3_yZ2oKo,1420
werkzeug/useragents.py,sha256=G8tmv_6vxJaPrLQH3eODNgIYe0_V6KETROQlJI-WxDE,7264
werkzeug/utils.py,sha256=WrU-LbwemyGd8zBHBgQyLaIxing4QLEChiP0qnzr2sc,36771
werkzeug/wrappers/__init__.py,sha256=-s75nPbyXHzU_rwmLPDhoMuGbEUk0jZT_n0ZQAOFGf8,654
werkzeug/wrappers/__pycache__/__init__.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/accept.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/auth.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/base_request.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/base_response.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/common_descriptors.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/cors.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/etag.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/json.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/request.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/response.cpython-311.pyc,,
werkzeug/wrappers/__pycache__/user_agent.cpython-311.pyc,,
werkzeug/wrappers/accept.py,sha256=_oZtAQkahvsrPRkNj2fieg7_St9P0NFC3SgZbJKS6xU,429
werkzeug/wrappers/auth.py,sha256=rZPCzGxHk9R55PRkmS90kRywUVjjuMWzCGtH68qCq8U,856
werkzeug/wrappers/base_request.py,sha256=saz9RyNQkvI_XLPYVm29KijNHmD1YzgxDqa0qHTbgss,1174
werkzeug/wrappers/base_response.py,sha256=q_-TaYywT5G4zA-DWDRDJhJSat2_4O7gOPob6ye4_9A,1186
werkzeug/wrappers/common_descriptors.py,sha256=v_kWLH3mvCiSRVJ1FNw7nO3w2UJfzY57UKKB5J4zCvE,898
werkzeug/wrappers/cors.py,sha256=c5UndlZsZvYkbPrp6Gj5iSXxw_VOJDJHskO6-jRmNyQ,846
werkzeug/wrappers/etag.py,sha256=XHWQQs7Mdd1oWezgBIsl-bYe8ydKkRZVil2Qd01D0Mo,846
werkzeug/wrappers/json.py,sha256=HM1btPseGeXca0vnwQN_MvZl6h-qNsFY5YBKXKXFwus,410
werkzeug/wrappers/request.py,sha256=0zAkCUwJbUBzioGy2UKxE6XpuXPAZbEhhML4WErzeBo,24818
werkzeug/wrappers/response.py,sha256=95hXIysZTeNC0bqhvGB2fLBRKxedR_cgI5szZZWfyzw,35177
werkzeug/wrappers/user_agent.py,sha256=Wl1-A0-1r8o7cHIZQTB55O4Ged6LpCKENaQDlOY5pXA,435
werkzeug/wsgi.py,sha256=7psV3SHLtCzk1KSuGmIK5uP2QTDXyfCCDclyqCmIUO4,33715

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.36.2)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1 @@
werkzeug

View File

@ -0,0 +1,222 @@
# don't import any costly modules
import sys
import os
is_pypy = '__pypy__' in sys.builtin_module_names
def warn_distutils_present():
if 'distutils' not in sys.modules:
return
if is_pypy and sys.version_info < (3, 7):
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
return
import warnings
warnings.warn(
"Distutils was imported before Setuptools, but importing Setuptools "
"also replaces the `distutils` module in `sys.modules`. This may lead "
"to undesirable behaviors or errors. To avoid these issues, avoid "
"using distutils directly, ensure that setuptools is installed in the "
"traditional way (e.g. not an editable install), and/or make sure "
"that setuptools is always imported before distutils."
)
def clear_distutils():
if 'distutils' not in sys.modules:
return
import warnings
warnings.warn("Setuptools is replacing distutils.")
mods = [
name
for name in sys.modules
if name == "distutils" or name.startswith("distutils.")
]
for name in mods:
del sys.modules[name]
def enabled():
"""
Allow selection of distutils by environment variable.
"""
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
return which == 'local'
def ensure_local_distutils():
import importlib
clear_distutils()
# With the DistutilsMetaFinder in place,
# perform an import to cause distutils to be
# loaded from setuptools._distutils. Ref #2906.
with shim():
importlib.import_module('distutils')
# check that submodules load as expected
core = importlib.import_module('distutils.core')
assert '_distutils' in core.__file__, core.__file__
assert 'setuptools._distutils.log' not in sys.modules
def do_override():
"""
Ensure that the local copy of distutils is preferred over stdlib.
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
for more motivation.
"""
if enabled():
warn_distutils_present()
ensure_local_distutils()
class _TrivialRe:
def __init__(self, *patterns):
self._patterns = patterns
def match(self, string):
return all(pat in string for pat in self._patterns)
class DistutilsMetaFinder:
def find_spec(self, fullname, path, target=None):
# optimization: only consider top level modules and those
# found in the CPython test suite.
if path is not None and not fullname.startswith('test.'):
return
method_name = 'spec_for_{fullname}'.format(**locals())
method = getattr(self, method_name, lambda: None)
return method()
def spec_for_distutils(self):
if self.is_cpython():
return
import importlib
import importlib.abc
import importlib.util
try:
mod = importlib.import_module('setuptools._distutils')
except Exception:
# There are a couple of cases where setuptools._distutils
# may not be present:
# - An older Setuptools without a local distutils is
# taking precedence. Ref #2957.
# - Path manipulation during sitecustomize removes
# setuptools from the path but only after the hook
# has been loaded. Ref #2980.
# In either case, fall back to stdlib behavior.
return
class DistutilsLoader(importlib.abc.Loader):
def create_module(self, spec):
mod.__name__ = 'distutils'
return mod
def exec_module(self, module):
pass
return importlib.util.spec_from_loader(
'distutils', DistutilsLoader(), origin=mod.__file__
)
@staticmethod
def is_cpython():
"""
Suppress supplying distutils for CPython (build and tests).
Ref #2965 and #3007.
"""
return os.path.isfile('pybuilddir.txt')
def spec_for_pip(self):
"""
Ensure stdlib distutils when running under pip.
See pypa/pip#8761 for rationale.
"""
if self.pip_imported_during_build():
return
clear_distutils()
self.spec_for_distutils = lambda: None
@classmethod
def pip_imported_during_build(cls):
"""
Detect if pip is being imported in a build script. Ref #2355.
"""
import traceback
return any(
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
)
@staticmethod
def frame_file_is_setup(frame):
"""
Return True if the indicated frame suggests a setup.py file.
"""
# some frames may not have __file__ (#2940)
return frame.f_globals.get('__file__', '').endswith('setup.py')
def spec_for_sensitive_tests(self):
"""
Ensure stdlib distutils when running select tests under CPython.
python/cpython#91169
"""
clear_distutils()
self.spec_for_distutils = lambda: None
sensitive_tests = (
[
'test.test_distutils',
'test.test_peg_generator',
'test.test_importlib',
]
if sys.version_info < (3, 10)
else [
'test.test_distutils',
]
)
for name in DistutilsMetaFinder.sensitive_tests:
setattr(
DistutilsMetaFinder,
f'spec_for_{name}',
DistutilsMetaFinder.spec_for_sensitive_tests,
)
DISTUTILS_FINDER = DistutilsMetaFinder()
def add_shim():
DISTUTILS_FINDER in sys.meta_path or insert_shim()
class shim:
def __enter__(self):
insert_shim()
def __exit__(self, exc, value, tb):
remove_shim()
def insert_shim():
sys.meta_path.insert(0, DISTUTILS_FINDER)
def remove_shim():
try:
sys.meta_path.remove(DISTUTILS_FINDER)
except ValueError:
pass

Some files were not shown because too many files have changed in this diff Show More