initial commit
This commit is contained in:
commit
192ada8e94
75
README.md
Normal file
75
README.md
Normal 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
|
||||
```
|
||||
BIN
__pycache__/app.cpython-312.pyc
Normal file
BIN
__pycache__/app.cpython-312.pyc
Normal file
Binary file not shown.
17
requirements.txt
Normal file
17
requirements.txt
Normal 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
|
||||
BIN
shared_uploads/083bdd21-37f4-404d-a64d-a0806c5b4119/dwg.xlsx
Normal file
BIN
shared_uploads/083bdd21-37f4-404d-a64d-a0806c5b4119/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/1627370c-d863-45c8-89fe-b68b630fed66/dwg.xlsx
Normal file
BIN
shared_uploads/1627370c-d863-45c8-89fe-b68b630fed66/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/21dc5524-2817-4218-9804-01fc8f7e27d8/dwg.xlsx
Normal file
BIN
shared_uploads/21dc5524-2817-4218-9804-01fc8f7e27d8/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/278c73b6-a26a-4f98-8cec-36983aa94653/dwg.xlsx
Normal file
BIN
shared_uploads/278c73b6-a26a-4f98-8cec-36983aa94653/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/3672371f-9a7d-4890-b8c3-71ce1371620d/dwg.xlsx
Normal file
BIN
shared_uploads/3672371f-9a7d-4890-b8c3-71ce1371620d/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/4d48b201-b8c3-4c49-bb9f-b3eba423acb5/dwg.xlsx
Normal file
BIN
shared_uploads/4d48b201-b8c3-4c49-bb9f-b3eba423acb5/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/52f1e0c4-70bf-42a4-84c2-6c67c8d3454c/dwg.xlsx
Normal file
BIN
shared_uploads/52f1e0c4-70bf-42a4-84c2-6c67c8d3454c/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/687057f4-d3cf-44db-93e0-252f23c32700/dwg.xlsx
Normal file
BIN
shared_uploads/687057f4-d3cf-44db-93e0-252f23c32700/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/74ab9aee-6567-44df-a924-5baebe55127b/dwg.xlsx
Normal file
BIN
shared_uploads/74ab9aee-6567-44df-a924-5baebe55127b/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/8f9ea4a1-7050-40ca-9bac-4e3be767142a/dwg.xlsx
Normal file
BIN
shared_uploads/8f9ea4a1-7050-40ca-9bac-4e3be767142a/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/d7b0f59a-651a-4c01-bb3a-c276f657692e/dwg.xlsx
Normal file
BIN
shared_uploads/d7b0f59a-651a-4c01-bb3a-c276f657692e/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/de49b68d-5c07-4eb2-b6b9-531a2d531b9a/dwg.xlsx
Normal file
BIN
shared_uploads/de49b68d-5c07-4eb2-b6b9-531a2d531b9a/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/de66a534-ba0d-4f10-900f-2970735a7e46/dwg.xlsx
Normal file
BIN
shared_uploads/de66a534-ba0d-4f10-900f-2970735a7e46/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/e0fe9fc4-e879-4183-895b-bf78d09d1f53/dwg.xlsx
Normal file
BIN
shared_uploads/e0fe9fc4-e879-4183-895b-bf78d09d1f53/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/f510173d-b9ca-4b4b-9dec-841d53f18db9/dwg.xlsx
Normal file
BIN
shared_uploads/f510173d-b9ca-4b4b-9dec-841d53f18db9/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
shared_uploads/fb084555-6e45-4173-92d4-4890cb2b6f26/dwg.xlsx
Normal file
BIN
shared_uploads/fb084555-6e45-4173-92d4-4890cb2b6f26/dwg.xlsx
Normal file
Binary file not shown.
Binary file not shown.
266
static/css/style.css
Normal file
266
static/css/style.css
Normal 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
183
static/js/original_names.js
Normal 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
402
static/js/script.js
Normal 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 = '×';
|
||||
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
BIN
temp_uploads/dwg.xlsx
Normal file
Binary file not shown.
BIN
temp_uploads/manifest.xlsx
Normal file
BIN
temp_uploads/manifest.xlsx
Normal file
Binary file not shown.
58
templates/base.html
Normal file
58
templates/base.html
Normal 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
209
templates/index.html
Normal 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
843
templates/results.html
Normal 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
247
venv/bin/Activate.ps1
Normal 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
63
venv/bin/activate
Normal 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
26
venv/bin/activate.csh
Normal 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
69
venv/bin/activate.fish
Normal 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
8
venv/bin/f2py
Executable 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
8
venv/bin/flask
Executable 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
8
venv/bin/numpy-config
Executable 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
8
venv/bin/pip
Executable 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
8
venv/bin/pip3
Executable 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
8
venv/bin/pip3.11
Executable 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
1
venv/bin/python
Symbolic link
@ -0,0 +1 @@
|
||||
python3.11
|
||||
1
venv/bin/python3
Symbolic link
1
venv/bin/python3
Symbolic link
@ -0,0 +1 @@
|
||||
python3.11
|
||||
1
venv/bin/python3.11
Symbolic link
1
venv/bin/python3.11
Symbolic link
@ -0,0 +1 @@
|
||||
/usr/bin/python3.11
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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.
|
||||
124
venv/lib/python3.11/site-packages/Flask-2.0.1.dist-info/METADATA
Normal file
124
venv/lib/python3.11/site-packages/Flask-2.0.1.dist-info/METADATA
Normal 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
|
||||
|
||||
|
||||
@ -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
|
||||
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.36.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
[console_scripts]
|
||||
flask = flask.cli:main
|
||||
|
||||
@ -0,0 +1 @@
|
||||
flask
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.37.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -0,0 +1 @@
|
||||
flask_wtf
|
||||
@ -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.
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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.
|
||||
@ -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"
|
||||
|
||||

|
||||
[](https://readthedocs.org/projects/gitpython/?badge=stable)
|
||||
[](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
|
||||
@ -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
|
||||
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (75.6.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -0,0 +1 @@
|
||||
git
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.36.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
[babel.extractors]
|
||||
jinja2 = jinja2.ext:babel_extract [i18n]
|
||||
|
||||
@ -0,0 +1 @@
|
||||
jinja2
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.36.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -0,0 +1 @@
|
||||
werkzeug
|
||||
Binary file not shown.
222
venv/lib/python3.11/site-packages/_distutils_hack/__init__.py
Normal file
222
venv/lib/python3.11/site-packages/_distutils_hack/__init__.py
Normal 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
|
||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user