parent
							
								
									d8f7645dd7
								
							
						
					
					
						commit
						1920aa81e0
					
				
							
								
								
									
										73
									
								
								index.js
								
								
								
								
							
							
						
						
									
										73
									
								
								index.js
								
								
								
								
							| 
						 | 
					@ -1,73 +0,0 @@
 | 
				
			||||||
import express from "express";
 | 
					 | 
				
			||||||
import {Liquid} from 'liquidjs';
 | 
					 | 
				
			||||||
import path from "path";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const liquidEngine = new Liquid({root: path.resolve(__dirname, 'views/'), jsTruthy: true});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const app = express();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.use(express.json()); // to support JSON-encoded bodies
 | 
					 | 
				
			||||||
app.use(express.urlencoded()); // to support URL-encoded bodies
 | 
					 | 
				
			||||||
app.engine('liquid', liquidEngine.express());
 | 
					 | 
				
			||||||
app.set('views', [path.join(__dirname, 'views')]);
 | 
					 | 
				
			||||||
app.set('view engine', 'liquid');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.use('/static', express.static('static'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.get('/', (req, res) => {
 | 
					 | 
				
			||||||
    res.render('index');
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function extractInfoFromPythonDepString(depstring) {
 | 
					 | 
				
			||||||
    const info = {
 | 
					 | 
				
			||||||
        name: "",
 | 
					 | 
				
			||||||
        version_operator: "",
 | 
					 | 
				
			||||||
        version: "",
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const parts = depstring.split(';');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const namever = parts[0];
 | 
					 | 
				
			||||||
    // https://stackoverflow.com/questions/24470567/what-are-the-requirements-for-naming-python-modules
 | 
					 | 
				
			||||||
    const match = namever.match(/^(?<name>[a-zA-Z_][a-zA-Z_0-9]*)(?<operator>.=)(?<version>[0-9.])/);
 | 
					 | 
				
			||||||
    if(match !== null && match.groups !== undefined) {
 | 
					 | 
				
			||||||
        info.name = match.groups['name'];
 | 
					 | 
				
			||||||
        info.operator = match.groups['operator'];
 | 
					 | 
				
			||||||
        info.version = match.groups['version'];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return info;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.post('/api/dependency', async (req, res) => {
 | 
					 | 
				
			||||||
    const packagename = req.body.package;
 | 
					 | 
				
			||||||
    console.log(packagename)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    const pypires = await fetch(`https://pypi.org/pypi/${packagename}/json`);
 | 
					 | 
				
			||||||
    const json = await pypires.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const name = json.info.name;
 | 
					 | 
				
			||||||
    const summary = json.info.summary;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const deps = json.info.requires_dist.map( e => extractInfoFromPythonDepString(e) );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const versions = json.releases;
 | 
					 | 
				
			||||||
    const latestVersionNumber = Object.entries(versions).sort( ([k1, v1], [k2, v2]) => k2 - v2 )[0];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const latestVersion = versions[latestVersionNumber];
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    const size = latestVersion.sort((a, b) => b.size - a.size )[0].size;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    res.render('dependency', {
 | 
					 | 
				
			||||||
        name: name,
 | 
					 | 
				
			||||||
        summary: summary,
 | 
					 | 
				
			||||||
        weight: size,
 | 
					 | 
				
			||||||
        dependencies: deps
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.listen(8080)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,552 @@
 | 
				
			||||||
 | 
					import path from 'node:path';
 | 
				
			||||||
 | 
					import express from 'express';
 | 
				
			||||||
 | 
					import {Liquid} from 'liquidjs';
 | 
				
			||||||
 | 
					import {RateLimit} from 'async-sema';
 | 
				
			||||||
 | 
					import fileUpload from 'express-fileupload';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Graph from 'graphology';
 | 
				
			||||||
 | 
					import forceAtlas2 from 'graphology-layout-forceatlas2';
 | 
				
			||||||
 | 
					import circular from 'graphology-layout/circular';
 | 
				
			||||||
 | 
					import {assignLayout} from 'graphology-layout/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {singleSource, singleSourceLength} from 'graphology-shortest-path/unweighted';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Pep440Version, {Pep440VersionRule} from './pep440version.ts';
 | 
				
			||||||
 | 
					import {version} from 'bun';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const liquidEngine = new Liquid({root: path.resolve(__dirname, 'views/'), jsTruthy: true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const app = express();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use(express.json()); // To support JSON-encoded bodies
 | 
				
			||||||
 | 
					app.use(express.urlencoded()); // To support URL-encoded bodies
 | 
				
			||||||
 | 
					app.use(fileUpload({
 | 
				
			||||||
 | 
					  limits: { fileSize: 50 * 1024 * 1024 },
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.engine('liquid', liquidEngine.express());
 | 
				
			||||||
 | 
					app.set('views', [path.join(__dirname, 'views')]);
 | 
				
			||||||
 | 
					app.set('view engine', 'liquid');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use('/static', express.static('static'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.get('/', (request, res) => {
 | 
				
			||||||
 | 
						res.render('index');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare class RateLimit {
 | 
				
			||||||
 | 
						constructor(max: number);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rateLimit: any = new RateLimit(5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function extractInfoFromPythonDepString(depstring: string) {
 | 
				
			||||||
 | 
						const parts = depstring.split(';');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const namever = parts[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// https://stackoverflow.com/questions/24470567/what-are-the-requirements-for-naming-python-modules
 | 
				
			||||||
 | 
						const match = namever.match(/^(?<name>[a-zA-Z_\-][\w\-]*)((?<operator>.=)(?<version>.+))?/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (match !== null && match.groups !== undefined) {
 | 
				
			||||||
 | 
							let rule;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( match.groups.version ) {
 | 
				
			||||||
 | 
								const version = new Pep440Version(match.groups.version);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								rule = new Pep440VersionRule(
 | 
				
			||||||
 | 
									version, 
 | 
				
			||||||
 | 
									match.groups.operator
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else {
 | 
				
			||||||
 | 
								rule = Pep440VersionRule.getRuleMatchingAnyVersion()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								name: match.groups.name,
 | 
				
			||||||
 | 
								rule: rule
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else {
 | 
				
			||||||
 | 
							return undefined;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function getRawPackageInfo(packagename: string) {
 | 
				
			||||||
 | 
						await rateLimit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const pypires = await fetch(`https://pypi.org/pypi/${packagename}/json`);
 | 
				
			||||||
 | 
						const json = await pypires.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return json;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface PipReleaseFile {
 | 
				
			||||||
 | 
						size: number;
 | 
				
			||||||
 | 
						packagetype: string;
 | 
				
			||||||
 | 
						upload_time_iso_8601: string;
 | 
				
			||||||
 | 
						[key: string]: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function getFullPackageInfo(packagename: string, rule: Pep440VersionRule = Pep440VersionRule.getRuleMatchingAnyVersion(), fromGraph = new Graph()) {
 | 
				
			||||||
 | 
						const dependencyGraph = fromGraph;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Analyse the requested package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const packageJson = await getRawPackageInfo(packagename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let longestDependencyChain = 0;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						//const dependenciesList = packageJson.info.requires_dist ?? [];
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						const recurseDepsPackageInfo = async (packagename: string, rule: Pep440VersionRule = Pep440VersionRule.getRuleMatchingAnyVersion(), descPath: string = "") => {
 | 
				
			||||||
 | 
							console.log(`[Recurse] Going to scan package ${packagename}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( descPath == "" ) {
 | 
				
			||||||
 | 
								descPath = packagename;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else {
 | 
				
			||||||
 | 
								descPath += `/${packagename}`
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( dependencyGraph.hasNode(packagename) ) {
 | 
				
			||||||
 | 
								// figure out if the current path taken to this package is shorter than the one in the graph
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// have to add the current package name manually as it isn't added yet
 | 
				
			||||||
 | 
								const paramPathParts: string[] = descPath.split('/');
 | 
				
			||||||
 | 
								const graphPathParts: string[] = dependencyGraph.getNodeAttribute(packagename, 'path').split('/');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if( paramPathParts.length < graphPathParts.length ) {
 | 
				
			||||||
 | 
									// if so, change the graph path to the current one
 | 
				
			||||||
 | 
									dependencyGraph.setNodeAttribute(packagename, 'path', descPath)
 | 
				
			||||||
 | 
									console.log(`[Recurse] Found better way of getting to ${packagename}: ${descPath}`);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const shortestDist = Math.min( paramPathParts.length, graphPathParts.length );
 | 
				
			||||||
 | 
								if( longestDependencyChain < shortestDist ) {
 | 
				
			||||||
 | 
									longestDependencyChain = shortestDist;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
								console.log(`[Recurse] Already scanned ${packagename} ; skipping.`);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							console.log(`[Recurse] Scanning package ${packagename}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const json = await getRawPackageInfo(packagename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ==================================================
 | 
				
			||||||
 | 
							// 1. get biggest file of highest possible version
 | 
				
			||||||
 | 
							// TODO: don't choose biggest file, choose in a smart way (like asking the user's OS and arch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const releasesEntries = Object.entries(json.releases as {[key: string]: PipReleaseFile[]});
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							const releases: {[key: string]: PipReleaseFile[]} = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for( const [version, data] of releasesEntries ) {
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									releases[new Pep440Version(version).releaseString()] = data;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch {
 | 
				
			||||||
 | 
									console.log(`Failed to parse version: ${version}`);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const versions = Object.keys(releases).map( v => {
 | 
				
			||||||
 | 
								console.log(`[Recurse] Parsing version ${v} for ${packagename}`);
 | 
				
			||||||
 | 
								return new Pep440Version(v);
 | 
				
			||||||
 | 
							}); 
 | 
				
			||||||
 | 
							const highestVersions = rule.getSortedMatchingVersions(versions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( highestVersions.length === 0 ) {
 | 
				
			||||||
 | 
								throw new Error("MERDE!");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							let highestVersion: undefined | Pep440Version = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for( const version of highestVersions ) {
 | 
				
			||||||
 | 
								if( releases[version.releaseString()].length !== 0 ) {
 | 
				
			||||||
 | 
									highestVersion = version;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( highestVersion === undefined ) {
 | 
				
			||||||
 | 
								throw new Error("MERDE!");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const highestRelease = releases[highestVersion.releaseString()];
 | 
				
			||||||
 | 
							const biggestFile = highestRelease.sort( (a, b) => b.size - a.size )[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							console.log(`[Recurse] ${packagename} has highest version ${highestVersion.releaseString()}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ==================================================
 | 
				
			||||||
 | 
							// 2. add to the graph
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const summary = json.summary;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
							dependencyGraph.addNode(packagename, {
 | 
				
			||||||
 | 
								label: packagename,
 | 
				
			||||||
 | 
								size: 10,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								summary: summary,
 | 
				
			||||||
 | 
								version: highestVersion.releaseString(),
 | 
				
			||||||
 | 
								weight: biggestFile.size,
 | 
				
			||||||
 | 
								path: descPath
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/////////////////
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const dependenciesList = packageJson.info.requires_dist ?? [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							console.log(`[Recurse] ${packagename} has deps:\n - ${dependenciesList.join('\n - ')}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for( const depString of dependenciesList ) {
 | 
				
			||||||
 | 
								console.log(`[Recurse] ${packagename} depends on: ${depString}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const info = extractInfoFromPythonDepString(depString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if( info === undefined ) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								await recurseDepsPackageInfo(info.name, info.rule, descPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if( dependencyGraph.hasEdge(packagename, info.name) === false ) {
 | 
				
			||||||
 | 
									dependencyGraph.addEdge(packagename, info.name);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await recurseDepsPackageInfo(packagename, rule);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`Longest dependency chain is ${longestDependencyChain}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//const paths = singleSource(dependencyGraph, packagename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const depthColors = [
 | 
				
			||||||
 | 
							"#D14D41",
 | 
				
			||||||
 | 
							"#DA702C",
 | 
				
			||||||
 | 
							"#D0A215",
 | 
				
			||||||
 | 
							"#879A39",
 | 
				
			||||||
 | 
							"#3AA99F",
 | 
				
			||||||
 | 
							"#4385BE",
 | 
				
			||||||
 | 
							"#8B7EC8",
 | 
				
			||||||
 | 
							"#CE5D97",
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const lengthmap = singleSourceLength(dependencyGraph, packagename);
 | 
				
			||||||
 | 
						const longestPathLength = Math.max(...Object.values(lengthmap))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for( const [node, dist] of Object.entries(lengthmap) ) {
 | 
				
			||||||
 | 
							dependencyGraph.setNodeAttribute(node, 'color', depthColors[dist + 1] )
 | 
				
			||||||
 | 
							dependencyGraph.setNodeAttribute(node, 'actual_depth', dist )
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dependencyGraph.forEachEdge((edge, attrs, source, target, sourceAttrs, targetAttrs) => {
 | 
				
			||||||
 | 
							const sourceDepth = sourceAttrs.actual_depth; //.path.split('/').length - 1;
 | 
				
			||||||
 | 
							const targetDepth = targetAttrs.actual_depth; //.path.split('/').length - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( sourceDepth >= targetDepth ) {
 | 
				
			||||||
 | 
								dependencyGraph.setEdgeAttribute( edge, "color", "#B7B5AC")
 | 
				
			||||||
 | 
								dependencyGraph.setEdgeAttribute( edge, "size", 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								dependencyGraph.setEdgeAttribute( edge, "weight", 1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else {
 | 
				
			||||||
 | 
								dependencyGraph.setEdgeAttribute( edge, "color", depthColors[ sourceDepth ])
 | 
				
			||||||
 | 
								dependencyGraph.setEdgeAttribute( edge, "size", 3 + 3 * (longestPathLength - sourceDepth))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								dependencyGraph.setEdgeAttribute( edge, "weight", 3 + 3 * (longestPathLength - sourceDepth))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							dependencyGraph.setEdgeAttribute( edge, "type", "arrow");
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//circular.assign(dependencyGraph, {scale: 100});
 | 
				
			||||||
 | 
						//forceAtlas2.assign(dependencyGraph, 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dependencyGraph.setNodeAttribute(packagename, 'size', 20)
 | 
				
			||||||
 | 
						//dependencyGraph.setNodeAttribute(packagename, 'x', 0)
 | 
				
			||||||
 | 
						//dependencyGraph.setNodeAttribute(packagename, 'y', 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						circular.assign(dependencyGraph, {scale: 100});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						const positions = forceAtlas2(dependencyGraph, {
 | 
				
			||||||
 | 
							iterations: 50,
 | 
				
			||||||
 | 
							settings: {
 | 
				
			||||||
 | 
								edgeWeightInfluence: 1,
 | 
				
			||||||
 | 
								...forceAtlas2.inferSettings(dependencyGraph),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assignLayout(dependencyGraph, positions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return dependencyGraph;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.get('/graph/:packagename', (req, res) => {
 | 
				
			||||||
 | 
						const packagename = req.params.packagename;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.render('graph', {
 | 
				
			||||||
 | 
							packagename: packagename,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}); 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.get('/api/graph/:packagename', async (req, res) => {
 | 
				
			||||||
 | 
						const packagename = req.params.packagename;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const depGraph = await getFullPackageInfo(packagename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const serializedDepGraph = depGraph.export();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let totalDownloadSize = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						depGraph.forEachNode((node, attrs) => {
 | 
				
			||||||
 | 
							totalDownloadSize += attrs.weight;
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						const units = [
 | 
				
			||||||
 | 
							'b',
 | 
				
			||||||
 | 
							'kb',
 | 
				
			||||||
 | 
							'Mb',
 | 
				
			||||||
 | 
							'Gb'
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let currentUnit = 0;
 | 
				
			||||||
 | 
						while( totalDownloadSize > 1000 ) {
 | 
				
			||||||
 | 
							totalDownloadSize = totalDownloadSize / 1000;
 | 
				
			||||||
 | 
							currentUnit++;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const unit = units[currentUnit];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.json({
 | 
				
			||||||
 | 
							weight: totalDownloadSize,
 | 
				
			||||||
 | 
							weightUnit: unit,
 | 
				
			||||||
 | 
							graph: serializedDepGraph
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.post('/api/upload-requirements', async (req, res) => {
 | 
				
			||||||
 | 
						// get the uploaded requirements.txt file, from the input with the name "file"
 | 
				
			||||||
 | 
						const files = req.files;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if( files === undefined || files === null ) {
 | 
				
			||||||
 | 
							res.status(400);
 | 
				
			||||||
 | 
							res.send("No file uploaded");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const file = files.requirementsFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if( file === undefined || file === null ) {
 | 
				
			||||||
 | 
							res.status(400);
 | 
				
			||||||
 | 
							res.send("No file uploaded");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if( Array.isArray(file) ) {
 | 
				
			||||||
 | 
							res.status(400);
 | 
				
			||||||
 | 
							res.send("Don't send multiple files.");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// convert to string
 | 
				
			||||||
 | 
						const content = '' + file.data;
 | 
				
			||||||
 | 
						const infos = content.replaceAll('\r\n', '\n').split('\n').map(extractInfoFromPythonDepString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let dependencyGraph = new Graph();
 | 
				
			||||||
 | 
						for( const info of infos ) {
 | 
				
			||||||
 | 
							if( info === undefined ) {
 | 
				
			||||||
 | 
								console.log(`Got an undefined info !`)
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							dependencyGraph = await getFullPackageInfo(info.name, info.rule, dependencyGraph);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const serializedDepGraph = dependencyGraph.export();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const getHumanWeight = (weight: number) => {
 | 
				
			||||||
 | 
							const units = [
 | 
				
			||||||
 | 
								'b',
 | 
				
			||||||
 | 
								'kb',
 | 
				
			||||||
 | 
								'Mb',
 | 
				
			||||||
 | 
								'Gb'
 | 
				
			||||||
 | 
							]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let currentUnit = 0;
 | 
				
			||||||
 | 
							while( weight > 1000 ) {
 | 
				
			||||||
 | 
								weight = weight / 1000;
 | 
				
			||||||
 | 
								currentUnit++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return `${Math.round(weight)}${units[currentUnit]}`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let nbNodes = 0;
 | 
				
			||||||
 | 
						let totalDownloadSize = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dependencyGraph.forEachNode((node, attrs) => {
 | 
				
			||||||
 | 
							nbNodes++;
 | 
				
			||||||
 | 
							totalDownloadSize += attrs.weight;
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let packageByWeight: {name: string, weight: number, humanWeight: string}[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dependencyGraph.forEachNode((node, attrs) => {
 | 
				
			||||||
 | 
							packageByWeight.push({
 | 
				
			||||||
 | 
								name: node,
 | 
				
			||||||
 | 
								weight: attrs.weight,
 | 
				
			||||||
 | 
								humanWeight: getHumanWeight(attrs.weight),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						packageByWeight = packageByWeight.sort( (a, b) => b.weight - a.weight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const humanDownloadSize = getHumanWeight(totalDownloadSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						res.render('fullgraph', {
 | 
				
			||||||
 | 
							//packagename: packagename,
 | 
				
			||||||
 | 
							nb_deps: nbNodes,
 | 
				
			||||||
 | 
							totalWeight: {
 | 
				
			||||||
 | 
								raw: totalDownloadSize,
 | 
				
			||||||
 | 
								value: humanDownloadSize,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							graph: serializedDepGraph,
 | 
				
			||||||
 | 
							packageByWeight: packageByWeight
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
						* Get info about a package
 | 
				
			||||||
 | 
						*/ /*
 | 
				
			||||||
 | 
					async function getFullPackageInfo(packagename: string, versionRequirement={operator: "", version: ""}, seenBefore: Set<string> = new Set([])) {
 | 
				
			||||||
 | 
						const packagejson = await getRawPackageInfo(packagename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const name = packagejson.info.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const summary = packagejson.info.summary;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const dependenciesList = packagejson.info.requires_dist ?? [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const builtDepsList: Map<string, {[key: string]: string]}>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for( const depString of dependenciesList ) {
 | 
				
			||||||
 | 
							const dep = extractInfoFromPythonDepString(depString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( seenBefore.has(dep.name) ) {
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const json = await getRawPackageInfo(dep.name, dep.version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					app.post('/api/dependency-tree', async (request, res) => {
 | 
				
			||||||
 | 
						const packagename = request.body.package;
 | 
				
			||||||
 | 
						console.log("Requested package: ", packagename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const json = await getRawPackageInfo(packagename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const name = json.info.name;
 | 
				
			||||||
 | 
						const summary = json.info.summary;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let deps = json.info.requires_dist ? json.info.requires_dist.map(e => extractInfoFromPythonDepString(e)) : [];
 | 
				
			||||||
 | 
						// avoid duplicates (sometimes packages list themselves as a dependency to create "dependency groups" (?))
 | 
				
			||||||
 | 
						deps = deps.filter( dep => dep.name !== packagename );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const versions = Object.entries(json.releases).filter( ([k, v]) => v.length > 0 );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// sort by size, preferring wheels
 | 
				
			||||||
 | 
						const versionsSorted = versions.map( ([versionNumber, filesArray]) => {
 | 
				
			||||||
 | 
								const wheels = filesArray.filter( file => file.packagetype === 'bdist_wheel' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if( wheels.length > 0 ) {
 | 
				
			||||||
 | 
									// sort by size
 | 
				
			||||||
 | 
									const biggestWheel = wheels.sort( (a, b) => b.size - a.size )[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return [versionNumber, biggestWheel];
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else {
 | 
				
			||||||
 | 
									const biggestFile = filesArray.sort( (a, b) => b.size - a.size )[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return [versionNumber, biggestFile];
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//const sorted = filesArray.sort( (a, b) => b.upload_time_iso8601 - a.upload_time_iso8601 );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const [latestVersionNumber, latestVersion] = versions[0];
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						const sortedFiles = latestVersion.sort((a, b) => b.size - a.size);
 | 
				
			||||||
 | 
						const size = sortedFiles[0].size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.render('dependency', {
 | 
				
			||||||
 | 
							name,
 | 
				
			||||||
 | 
							summary,
 | 
				
			||||||
 | 
							weight: size,
 | 
				
			||||||
 | 
							dependencies: deps,
 | 
				
			||||||
 | 
							// DEBUG
 | 
				
			||||||
 | 
							has_parent: request.body.isfirst !== "true",
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.listen(8080);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								package.json
								
								
								
								
							
							
						
						
									
										28
									
								
								package.json
								
								
								
								
							| 
						 | 
					@ -1 +1,27 @@
 | 
				
			||||||
{"dependencies":{"express":"^4.19.2","liquidjs":"^10.10.2"},"name":"depyth","module":"index.js","type":"module","devDependencies":{"@types/bun":"latest"},"peerDependencies":{"typescript":"^5.0.0"}}
 | 
					{
 | 
				
			||||||
 | 
					  "name": "depyth",
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "async-sema": "^3.1.1",
 | 
				
			||||||
 | 
					    "express": "^4.19.2",
 | 
				
			||||||
 | 
					    "express-fileupload": "^1.5.0",
 | 
				
			||||||
 | 
					    "graphology": "^0.25.4",
 | 
				
			||||||
 | 
					    "graphology-layout": "^0.6.1",
 | 
				
			||||||
 | 
					    "graphology-layout-forceatlas2": "^0.10.1",
 | 
				
			||||||
 | 
					    "graphology-shortest-path": "^2.1.0",
 | 
				
			||||||
 | 
					    "liquidjs": "^10.10.2",
 | 
				
			||||||
 | 
					    "melodyc": "^0.19.0"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "css:watch": "npx tailwindcss -i ./input.css -o ./static/styles.css --watch",
 | 
				
			||||||
 | 
					    "css:watch:prod": "npx tailwindcss -i ./input.css -o ./static/styles.css --watch --minify"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "module": "index.js",
 | 
				
			||||||
 | 
					  "type": "module",
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@types/bun": "latest",
 | 
				
			||||||
 | 
					    "graphology-types": "^0.24.7"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "peerDependencies": {
 | 
				
			||||||
 | 
					    "typescript": "^5.0.0"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,416 @@
 | 
				
			||||||
 | 
					import { compiler } from "melodyc";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					VERSION_PATTERN = r"""
 | 
				
			||||||
 | 
					    v?
 | 
				
			||||||
 | 
					    (?:
 | 
				
			||||||
 | 
					        (?:(?P<epoch>[0-9]+)!)?                           # epoch
 | 
				
			||||||
 | 
					        (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment
 | 
				
			||||||
 | 
					        (?P<pre>                                          # pre-release
 | 
				
			||||||
 | 
					            [-_\.]?
 | 
				
			||||||
 | 
					            (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
 | 
				
			||||||
 | 
					            [-_\.]?
 | 
				
			||||||
 | 
					            (?P<pre_n>[0-9]+)?
 | 
				
			||||||
 | 
					        )?
 | 
				
			||||||
 | 
					        (?P<post>                                         # post release
 | 
				
			||||||
 | 
					            (?:-(?P<post_n1>[0-9]+))
 | 
				
			||||||
 | 
					            |
 | 
				
			||||||
 | 
					            (?:
 | 
				
			||||||
 | 
					                [-_\.]?
 | 
				
			||||||
 | 
					                (?P<post_l>post|rev|r)
 | 
				
			||||||
 | 
					                [-_\.]?
 | 
				
			||||||
 | 
					                (?P<post_n2>[0-9]+)?
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )?
 | 
				
			||||||
 | 
					        (?P<dev>                                          # dev release
 | 
				
			||||||
 | 
					            [-_\.]?
 | 
				
			||||||
 | 
					            (?P<dev_l>dev)
 | 
				
			||||||
 | 
					            [-_\.]?
 | 
				
			||||||
 | 
					            (?P<dev_n>[0-9]+)?
 | 
				
			||||||
 | 
					        )?
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const VERSION_PATTERN = `
 | 
				
			||||||
 | 
						<start>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						option of "v";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						match {
 | 
				
			||||||
 | 
							option of match {
 | 
				
			||||||
 | 
								capture epoch {
 | 
				
			||||||
 | 
									some of <digit>;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								"!";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							capture release {
 | 
				
			||||||
 | 
								some of <digit>;
 | 
				
			||||||
 | 
								any of match {
 | 
				
			||||||
 | 
									".";
 | 
				
			||||||
 | 
									some of <digit>;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							option of capture pre {
 | 
				
			||||||
 | 
								option of either {
 | 
				
			||||||
 | 
									".";
 | 
				
			||||||
 | 
									"_";
 | 
				
			||||||
 | 
									"-";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								capture pre_l {
 | 
				
			||||||
 | 
									either {
 | 
				
			||||||
 | 
										"a";
 | 
				
			||||||
 | 
										"b";
 | 
				
			||||||
 | 
										"c";
 | 
				
			||||||
 | 
										"rc";
 | 
				
			||||||
 | 
										"alpha";
 | 
				
			||||||
 | 
										"beta";
 | 
				
			||||||
 | 
										"pre";
 | 
				
			||||||
 | 
										"preview";
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								option of either {
 | 
				
			||||||
 | 
									".";
 | 
				
			||||||
 | 
									"_";
 | 
				
			||||||
 | 
									"-";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								capture pre_n {
 | 
				
			||||||
 | 
									some of <digit>;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							option of capture post {
 | 
				
			||||||
 | 
								either {
 | 
				
			||||||
 | 
									match {
 | 
				
			||||||
 | 
										"-";
 | 
				
			||||||
 | 
										capture post_n {
 | 
				
			||||||
 | 
											some of <digit>;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									match {
 | 
				
			||||||
 | 
										option of either {
 | 
				
			||||||
 | 
											".";
 | 
				
			||||||
 | 
											"_";
 | 
				
			||||||
 | 
											"-";
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										capture post_l {
 | 
				
			||||||
 | 
											either {
 | 
				
			||||||
 | 
												"post";
 | 
				
			||||||
 | 
												"rev";
 | 
				
			||||||
 | 
												"r";
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										option of either {
 | 
				
			||||||
 | 
											".";
 | 
				
			||||||
 | 
											"_";
 | 
				
			||||||
 | 
											"-";
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										option of capture post_n {
 | 
				
			||||||
 | 
											some of <digit>;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							option of capture dev {
 | 
				
			||||||
 | 
								option of either {
 | 
				
			||||||
 | 
									".";
 | 
				
			||||||
 | 
									"_";
 | 
				
			||||||
 | 
									"-";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								capture dev_l {
 | 
				
			||||||
 | 
									"dev";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								option of either {
 | 
				
			||||||
 | 
									".";
 | 
				
			||||||
 | 
									"_";
 | 
				
			||||||
 | 
									"-";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								option of capture dev_n {
 | 
				
			||||||
 | 
									some of <digit>;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							option of match {
 | 
				
			||||||
 | 
								"+";
 | 
				
			||||||
 | 
								capture local {
 | 
				
			||||||
 | 
									some of either {
 | 
				
			||||||
 | 
										a to z;
 | 
				
			||||||
 | 
										0 to 9;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									any of match {
 | 
				
			||||||
 | 
										option of either {
 | 
				
			||||||
 | 
											".";
 | 
				
			||||||
 | 
											"_";
 | 
				
			||||||
 | 
											"-";
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										some of either {
 | 
				
			||||||
 | 
											a to z;
 | 
				
			||||||
 | 
											0 to 9;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// get rid of random bullshit at the end
 | 
				
			||||||
 | 
						capture random_bullshit {
 | 
				
			||||||
 | 
							any of <char>;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<end>;
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const output = compiler(VERSION_PATTERN);
 | 
				
			||||||
 | 
					console.log(output);
 | 
				
			||||||
 | 
					const VERSION_REGEX = new RegExp(output);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class Pep440Version {
 | 
				
			||||||
 | 
						epoch?: number;
 | 
				
			||||||
 | 
						release: {
 | 
				
			||||||
 | 
							major: number;
 | 
				
			||||||
 | 
							minor: number;
 | 
				
			||||||
 | 
							patch: number;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						preRelease?: {
 | 
				
			||||||
 | 
							name: string;
 | 
				
			||||||
 | 
							version?: number;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						postRelease?: {
 | 
				
			||||||
 | 
							name?: string;
 | 
				
			||||||
 | 
							version?: number;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						dev?: {
 | 
				
			||||||
 | 
							name: string;
 | 
				
			||||||
 | 
							version?: number;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						random_bullshit_at_the_end?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
							*
 | 
				
			||||||
 | 
							* @param {string} versionString
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
						constructor(versionString: string) {
 | 
				
			||||||
 | 
							console.log(`Constructing version from ${versionString}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const match = versionString.match(VERSION_REGEX);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( match === null || match.groups === undefined ) {
 | 
				
			||||||
 | 
								throw new Error("Couldn't parse version: " + versionString)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const g = match.groups;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( g['release'] === undefined ) {
 | 
				
			||||||
 | 
								throw new Error("Couldn't parse version (because of the absence of a release): " + versionString)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const [ major, minor, patch ] = g['release'].split(".").map( e => Number(e) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.release = {
 | 
				
			||||||
 | 
								major: major ?? 0,
 | 
				
			||||||
 | 
								minor: minor ?? 0,
 | 
				
			||||||
 | 
								patch: patch ?? 0,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.epoch = Number(g['epoch']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( g['pre'] ) {
 | 
				
			||||||
 | 
								this.preRelease = {
 | 
				
			||||||
 | 
									name: g['pre_l'],
 | 
				
			||||||
 | 
									version: g['pre_n'] ? Number(g['pre_n']) : undefined,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( g['post'] ) {
 | 
				
			||||||
 | 
								this.postRelease = {
 | 
				
			||||||
 | 
									name: g['post_l'],
 | 
				
			||||||
 | 
									version: g['post_n'] ? Number(g['post_n']) : undefined,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( g['dev'] ) {
 | 
				
			||||||
 | 
								this.dev = {
 | 
				
			||||||
 | 
									name: g['dev_l'],
 | 
				
			||||||
 | 
									version: g['dev_n'] ? Number(g['dev_n']) : undefined,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.random_bullshit_at_the_end = g['random_bullshit'];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
							* @returns the version (as as tring) in a format that can be correctly compared
 | 
				
			||||||
 | 
							* even if it looks goofy
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
						toComparableString() {
 | 
				
			||||||
 | 
							// comparing strings is annoying
 | 
				
			||||||
 | 
							// for example: "1.2" > "1.12", even though 12 > 2
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// To fix that issue, we're going to have fixed-length numbers,
 | 
				
			||||||
 | 
							// so that comparing 1.2 and 1.12 would mean comparing
 | 
				
			||||||
 | 
							// "000012" > "000002", this one being correct because 1 > 0.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// pad to 8 because who in their right mind would use 8 digits for the major version
 | 
				
			||||||
 | 
							// (noting that some people use 4 because they use years)
 | 
				
			||||||
 | 
							const PAD_LENGTH = 8;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							const epoch = (this.epoch?.toString() ?? '0').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const release_major = (this.release.major.toString() ?? '0').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
							const release_minor = (this.release.minor.toString() ?? '0').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
							const release_patch = (this.release.patch.toString() ?? '0').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
							const release = `${release_major}.${release_minor}.${release_patch}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const prename = (this.preRelease?.name || '').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
							const postname = (this.postRelease?.name || '').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
							const devname = (this.dev?.name || '').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const prever = (this.preRelease?.version?.toString() || '0').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
							const postver = (this.postRelease?.version?.toString() || '0').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
							const devver = (this.dev?.version?.toString() || '0').padStart(PAD_LENGTH, '0');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return `${epoch}!${release}.${prename}${prever}.${postname}${postver}.${devname}${devver}`;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isAbove(other: Pep440Version) {
 | 
				
			||||||
 | 
							return this.toComparableString() > other.toComparableString();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isBelow(other: Pep440Version) {
 | 
				
			||||||
 | 
							return this.toComparableString() < other.toComparableString();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isEqual(other: Pep440Version) {
 | 
				
			||||||
 | 
							return this.toComparableString() === other.toComparableString();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compare(other: Pep440Version) {
 | 
				
			||||||
 | 
							if( this.isAbove(other) ) {
 | 
				
			||||||
 | 
								return -1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else if( this.isBelow(other) ) {
 | 
				
			||||||
 | 
								return 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else {
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
							* @returns the version as a Pep440-compliant, human-readable string (e.g. "2.1.3")
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
						releaseString() {
 | 
				
			||||||
 | 
							let str = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( this.epoch ) {
 | 
				
			||||||
 | 
								str += `${this.epoch}!`;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							str += this.release.major ?? 0;
 | 
				
			||||||
 | 
							str += '.';
 | 
				
			||||||
 | 
							str += this.release.minor ?? 0;
 | 
				
			||||||
 | 
							str += '.';
 | 
				
			||||||
 | 
							str += this.release.patch ?? 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(this.preRelease) {
 | 
				
			||||||
 | 
								str += '.';
 | 
				
			||||||
 | 
								if( this.preRelease.name ) {
 | 
				
			||||||
 | 
									str += this.preRelease.name;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if( this.preRelease.version ) {
 | 
				
			||||||
 | 
									str += this.preRelease.version;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(this.postRelease) {
 | 
				
			||||||
 | 
								str += '.';
 | 
				
			||||||
 | 
								if( this.postRelease.name ) {
 | 
				
			||||||
 | 
									str += this.postRelease.name;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if( this.postRelease.version ) {
 | 
				
			||||||
 | 
									str += this.postRelease.version;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(this.dev) {
 | 
				
			||||||
 | 
								str += '.';
 | 
				
			||||||
 | 
								if( this.dev.name ) {
 | 
				
			||||||
 | 
									str += this.dev.name;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if( this.dev.version ) {
 | 
				
			||||||
 | 
									str += this.dev.version;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if( this.random_bullshit_at_the_end ) {
 | 
				
			||||||
 | 
								str += this.random_bullshit_at_the_end;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return str;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//static fromString(...versionStrings: string[]) {
 | 
				
			||||||
 | 
						//	return versionStrings.map( v => new Pep440Version(v) );
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OPERATOR_FUNCTION = (source: Pep440Version, target: Pep440Version) => boolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OPERATORS_FN_type = {
 | 
				
			||||||
 | 
						[key: string]: OPERATOR_FUNCTION;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const OPERATORS_FUNCTIONS: OPERATORS_FN_type = {
 | 
				
			||||||
 | 
						">=": (source, target) => target.isAbove(source) || target.isEqual(source),
 | 
				
			||||||
 | 
						">": (source, target) => target.isAbove(source),
 | 
				
			||||||
 | 
						"==": (source, target) => target.isEqual(source),
 | 
				
			||||||
 | 
						"": (source, target) => true,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Pep440VersionRule {
 | 
				
			||||||
 | 
						version: Pep440Version;
 | 
				
			||||||
 | 
						operator: OPERATOR_FUNCTION;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(version: Pep440Version, operator: string = "") {
 | 
				
			||||||
 | 
							this.version = version;
 | 
				
			||||||
 | 
							this.operator = OPERATORS_FUNCTIONS[operator];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
						getSortedMatchingVersions(versions: Pep440Version[]) {
 | 
				
			||||||
 | 
							const sorted = versions.sort( (a, b) => a.compare(b) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let filtered;
 | 
				
			||||||
 | 
							if( typeof this.operator === "function" ) {
 | 
				
			||||||
 | 
								filtered = sorted.filter( v => this.operator( this.version, v ) );
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else {
 | 
				
			||||||
 | 
								filtered = sorted;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return filtered;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static getRuleMatchingAnyVersion() {
 | 
				
			||||||
 | 
							return new Pep440VersionRule(new Pep440Version("0.0.0"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//console.log( new Pep440Version("4!1.2.3dev2").toComparableString() )
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					<svg width="16" height="16" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#494949">
 | 
				
			||||||
 | 
					    <rect y="10" width="15" height="120" rx="6">
 | 
				
			||||||
 | 
					        <animate attributeName="height"
 | 
				
			||||||
 | 
					             begin="0.5s" dur="1s"
 | 
				
			||||||
 | 
					             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					        <animate attributeName="y"
 | 
				
			||||||
 | 
					             begin="0.5s" dur="1s"
 | 
				
			||||||
 | 
					             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					    </rect>
 | 
				
			||||||
 | 
					    <rect x="30" y="10" width="15" height="120" rx="6">
 | 
				
			||||||
 | 
					        <animate attributeName="height"
 | 
				
			||||||
 | 
					             begin="0.25s" dur="1s"
 | 
				
			||||||
 | 
					             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					        <animate attributeName="y"
 | 
				
			||||||
 | 
					             begin="0.25s" dur="1s"
 | 
				
			||||||
 | 
					             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					    </rect>
 | 
				
			||||||
 | 
					    <rect x="60" width="15" height="140" rx="6">
 | 
				
			||||||
 | 
					        <animate attributeName="height"
 | 
				
			||||||
 | 
					             begin="0s" dur="1s"
 | 
				
			||||||
 | 
					             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					        <animate attributeName="y"
 | 
				
			||||||
 | 
					             begin="0s" dur="1s"
 | 
				
			||||||
 | 
					             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					    </rect>
 | 
				
			||||||
 | 
					    <rect x="90" y="10" width="15" height="120" rx="6">
 | 
				
			||||||
 | 
					        <animate attributeName="height"
 | 
				
			||||||
 | 
					             begin="0.25s" dur="1s"
 | 
				
			||||||
 | 
					             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					        <animate attributeName="y"
 | 
				
			||||||
 | 
					             begin="0.25s" dur="1s"
 | 
				
			||||||
 | 
					             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					    </rect>
 | 
				
			||||||
 | 
					    <rect x="120" y="10" width="15" height="120" rx="6">
 | 
				
			||||||
 | 
					        <animate attributeName="height"
 | 
				
			||||||
 | 
					             begin="0.5s" dur="1s"
 | 
				
			||||||
 | 
					             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					        <animate attributeName="y"
 | 
				
			||||||
 | 
					             begin="0.5s" dur="1s"
 | 
				
			||||||
 | 
					             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
 | 
				
			||||||
 | 
					             repeatCount="indefinite" />
 | 
				
			||||||
 | 
					    </rect>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 2.3 KiB  | 
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
					@ -9,7 +9,7 @@
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
 | 
					! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
| 
						 | 
					@ -221,6 +221,8 @@ textarea {
 | 
				
			||||||
  /* 1 */
 | 
					  /* 1 */
 | 
				
			||||||
  line-height: inherit;
 | 
					  line-height: inherit;
 | 
				
			||||||
  /* 1 */
 | 
					  /* 1 */
 | 
				
			||||||
 | 
					  letter-spacing: inherit;
 | 
				
			||||||
 | 
					  /* 1 */
 | 
				
			||||||
  color: inherit;
 | 
					  color: inherit;
 | 
				
			||||||
  /* 1 */
 | 
					  /* 1 */
 | 
				
			||||||
  margin: 0;
 | 
					  margin: 0;
 | 
				
			||||||
| 
						 | 
					@ -244,9 +246,9 @@ select {
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
button,
 | 
					button,
 | 
				
			||||||
[type='button'],
 | 
					input:where([type='button']),
 | 
				
			||||||
[type='reset'],
 | 
					input:where([type='reset']),
 | 
				
			||||||
[type='submit'] {
 | 
					input:where([type='submit']) {
 | 
				
			||||||
  -webkit-appearance: button;
 | 
					  -webkit-appearance: button;
 | 
				
			||||||
  /* 1 */
 | 
					  /* 1 */
 | 
				
			||||||
  background-color: transparent;
 | 
					  background-color: transparent;
 | 
				
			||||||
| 
						 | 
					@ -509,6 +511,10 @@ html {
 | 
				
			||||||
  --tw-backdrop-opacity:  ;
 | 
					  --tw-backdrop-opacity:  ;
 | 
				
			||||||
  --tw-backdrop-saturate:  ;
 | 
					  --tw-backdrop-saturate:  ;
 | 
				
			||||||
  --tw-backdrop-sepia:  ;
 | 
					  --tw-backdrop-sepia:  ;
 | 
				
			||||||
 | 
					  --tw-contain-size:  ;
 | 
				
			||||||
 | 
					  --tw-contain-layout:  ;
 | 
				
			||||||
 | 
					  --tw-contain-paint:  ;
 | 
				
			||||||
 | 
					  --tw-contain-style:  ;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
::backdrop {
 | 
					::backdrop {
 | 
				
			||||||
| 
						 | 
					@ -559,6 +565,10 @@ html {
 | 
				
			||||||
  --tw-backdrop-opacity:  ;
 | 
					  --tw-backdrop-opacity:  ;
 | 
				
			||||||
  --tw-backdrop-saturate:  ;
 | 
					  --tw-backdrop-saturate:  ;
 | 
				
			||||||
  --tw-backdrop-sepia:  ;
 | 
					  --tw-backdrop-sepia:  ;
 | 
				
			||||||
 | 
					  --tw-contain-size:  ;
 | 
				
			||||||
 | 
					  --tw-contain-layout:  ;
 | 
				
			||||||
 | 
					  --tw-contain-paint:  ;
 | 
				
			||||||
 | 
					  --tw-contain-style:  ;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.container {
 | 
					.container {
 | 
				
			||||||
| 
						 | 
					@ -595,17 +605,20 @@ html {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.m-2 {
 | 
					.absolute {
 | 
				
			||||||
  margin: 0.5rem;
 | 
					  position: absolute;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.m-1 {
 | 
					.relative {
 | 
				
			||||||
  margin: 0.25rem;
 | 
					  position: relative;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.my-4 {
 | 
					.left-\[calc\(-1rem-1px\)\] {
 | 
				
			||||||
  margin-top: 1rem;
 | 
					  left: calc(-1rem - 1px);
 | 
				
			||||||
  margin-bottom: 1rem;
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.top-4 {
 | 
				
			||||||
 | 
					  top: 1rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mx-auto {
 | 
					.mx-auto {
 | 
				
			||||||
| 
						 | 
					@ -618,25 +631,27 @@ html {
 | 
				
			||||||
  margin-bottom: 0.25rem;
 | 
					  margin-bottom: 0.25rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.my-8 {
 | 
					.my-2 {
 | 
				
			||||||
  margin-top: 2rem;
 | 
					  margin-top: 0.5rem;
 | 
				
			||||||
 | 
					  margin-bottom: 0.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.my-3 {
 | 
				
			||||||
 | 
					  margin-top: 0.75rem;
 | 
				
			||||||
 | 
					  margin-bottom: 0.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.my-4 {
 | 
				
			||||||
 | 
					  margin-top: 1rem;
 | 
				
			||||||
 | 
					  margin-bottom: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mb-8 {
 | 
				
			||||||
  margin-bottom: 2rem;
 | 
					  margin-bottom: 2rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.ml-2 {
 | 
					.ml-8 {
 | 
				
			||||||
  margin-left: 0.5rem;
 | 
					  margin-left: 2rem;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.mt-1 {
 | 
					 | 
				
			||||||
  margin-top: 0.25rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.mt-2 {
 | 
					 | 
				
			||||||
  margin-top: 0.5rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.ml-1 {
 | 
					 | 
				
			||||||
  margin-left: 0.25rem;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.block {
 | 
					.block {
 | 
				
			||||||
| 
						 | 
					@ -647,10 +662,26 @@ html {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.grid {
 | 
				
			||||||
 | 
					  display: grid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.hidden {
 | 
					.hidden {
 | 
				
			||||||
  display: none;
 | 
					  display: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-4 {
 | 
				
			||||||
 | 
					  height: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-\[600px\] {
 | 
				
			||||||
 | 
					  height: 600px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-\[60vh\] {
 | 
				
			||||||
 | 
					  height: 60vh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.h-full {
 | 
					.h-full {
 | 
				
			||||||
  height: 100%;
 | 
					  height: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -665,6 +696,18 @@ html {
 | 
				
			||||||
  height: min-content;
 | 
					  height: min-content;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-\[80vh\] {
 | 
				
			||||||
 | 
					  height: 80vh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-16 {
 | 
				
			||||||
 | 
					  height: 4rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.min-h-72 {
 | 
				
			||||||
 | 
					  min-height: 18rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.min-h-max {
 | 
					.min-h-max {
 | 
				
			||||||
  min-height: -moz-max-content;
 | 
					  min-height: -moz-max-content;
 | 
				
			||||||
  min-height: max-content;
 | 
					  min-height: max-content;
 | 
				
			||||||
| 
						 | 
					@ -674,6 +717,10 @@ html {
 | 
				
			||||||
  min-height: 100vh;
 | 
					  min-height: 100vh;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.w-4 {
 | 
				
			||||||
 | 
					  width: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.w-fit {
 | 
					.w-fit {
 | 
				
			||||||
  width: -moz-fit-content;
 | 
					  width: -moz-fit-content;
 | 
				
			||||||
  width: fit-content;
 | 
					  width: fit-content;
 | 
				
			||||||
| 
						 | 
					@ -683,30 +730,6 @@ html {
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.min-w-\[50vw\] {
 | 
					 | 
				
			||||||
  min-width: 50vw;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.min-w-\[80vw\] {
 | 
					 | 
				
			||||||
  min-width: 80vw;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.max-w-\[600px\] {
 | 
					 | 
				
			||||||
  max-width: 600px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.max-w-\[300px\] {
 | 
					 | 
				
			||||||
  max-width: 300px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.max-w-\[200px\] {
 | 
					 | 
				
			||||||
  max-width: 200px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.max-w-\[20rem\] {
 | 
					 | 
				
			||||||
  max-width: 20rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.max-w-\[800px\] {
 | 
					.max-w-\[800px\] {
 | 
				
			||||||
  max-width: 800px;
 | 
					  max-width: 800px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -715,8 +738,13 @@ html {
 | 
				
			||||||
  flex: 1 1 0%;
 | 
					  flex: 1 1 0%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.list-disc {
 | 
					.-translate-y-1\/2 {
 | 
				
			||||||
  list-style-type: disc;
 | 
					  --tw-translate-y: -50%;
 | 
				
			||||||
 | 
					  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.grid-cols-\[auto_1fr\] {
 | 
				
			||||||
 | 
					  grid-template-columns: auto 1fr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.flex-col {
 | 
					.flex-col {
 | 
				
			||||||
| 
						 | 
					@ -731,14 +759,26 @@ html {
 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.border-2 {
 | 
					.gap-y-1 {
 | 
				
			||||||
  border-width: 2px;
 | 
					  row-gap: 0.25rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.border {
 | 
					.border {
 | 
				
			||||||
  border-width: 1px;
 | 
					  border-width: 1px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.border-2 {
 | 
				
			||||||
 | 
					  border-width: 2px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.border-4 {
 | 
				
			||||||
 | 
					  border-width: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.border-l-2 {
 | 
				
			||||||
 | 
					  border-left-width: 2px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.border-r-0 {
 | 
					.border-r-0 {
 | 
				
			||||||
  border-right-width: 0px;
 | 
					  border-right-width: 0px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -753,24 +793,19 @@ html {
 | 
				
			||||||
  background-color: rgb(14 116 144 / var(--tw-bg-opacity));
 | 
					  background-color: rgb(14 116 144 / var(--tw-bg-opacity));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.bg-emerald-400 {
 | 
					.bg-white {
 | 
				
			||||||
  --tw-bg-opacity: 1;
 | 
					  --tw-bg-opacity: 1;
 | 
				
			||||||
  background-color: rgb(52 211 153 / var(--tw-bg-opacity));
 | 
					  background-color: rgb(255 255 255 / var(--tw-bg-opacity));
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.bg-emerald-300 {
 | 
					 | 
				
			||||||
  --tw-bg-opacity: 1;
 | 
					 | 
				
			||||||
  background-color: rgb(110 231 183 / var(--tw-bg-opacity));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.p-4 {
 | 
					 | 
				
			||||||
  padding: 1rem;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.p-2 {
 | 
					.p-2 {
 | 
				
			||||||
  padding: 0.5rem;
 | 
					  padding: 0.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.p-4 {
 | 
				
			||||||
 | 
					  padding: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.px-2 {
 | 
					.px-2 {
 | 
				
			||||||
  padding-left: 0.5rem;
 | 
					  padding-left: 0.5rem;
 | 
				
			||||||
  padding-right: 0.5rem;
 | 
					  padding-right: 0.5rem;
 | 
				
			||||||
| 
						 | 
					@ -781,51 +816,22 @@ html {
 | 
				
			||||||
  padding-bottom: 0.25rem;
 | 
					  padding-bottom: 0.25rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.px-8 {
 | 
					.pl-2 {
 | 
				
			||||||
  padding-left: 2rem;
 | 
					  padding-left: 0.5rem;
 | 
				
			||||||
  padding-right: 2rem;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.py-8 {
 | 
					.pt-4 {
 | 
				
			||||||
  padding-top: 2rem;
 | 
					  padding-top: 1rem;
 | 
				
			||||||
  padding-bottom: 2rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pl-0 {
 | 
					 | 
				
			||||||
  padding-left: 0px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pl-4 {
 | 
					 | 
				
			||||||
  padding-left: 1rem;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.text-center {
 | 
					.text-center {
 | 
				
			||||||
  text-align: center;
 | 
					  text-align: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.align-middle {
 | 
					 | 
				
			||||||
  vertical-align: middle;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.font-mono {
 | 
					.font-mono {
 | 
				
			||||||
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
 | 
					  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.text-4xl {
 | 
					 | 
				
			||||||
  font-size: 2.25rem;
 | 
					 | 
				
			||||||
  line-height: 2.5rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.text-xl {
 | 
					 | 
				
			||||||
  font-size: 1.25rem;
 | 
					 | 
				
			||||||
  line-height: 1.75rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.text-sm {
 | 
					 | 
				
			||||||
  font-size: 0.875rem;
 | 
					 | 
				
			||||||
  line-height: 1.25rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.text-2xl {
 | 
					.text-2xl {
 | 
				
			||||||
  font-size: 1.5rem;
 | 
					  font-size: 1.5rem;
 | 
				
			||||||
  line-height: 2rem;
 | 
					  line-height: 2rem;
 | 
				
			||||||
| 
						 | 
					@ -836,15 +842,39 @@ html {
 | 
				
			||||||
  line-height: 2.25rem;
 | 
					  line-height: 2.25rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.text-4xl {
 | 
				
			||||||
 | 
					  font-size: 2.25rem;
 | 
				
			||||||
 | 
					  line-height: 2.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.text-5xl {
 | 
					.text-5xl {
 | 
				
			||||||
  font-size: 3rem;
 | 
					  font-size: 3rem;
 | 
				
			||||||
  line-height: 1;
 | 
					  line-height: 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.text-sm {
 | 
				
			||||||
 | 
					  font-size: 0.875rem;
 | 
				
			||||||
 | 
					  line-height: 1.25rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.text-lg {
 | 
				
			||||||
 | 
					  font-size: 1.125rem;
 | 
				
			||||||
 | 
					  line-height: 1.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.text-xl {
 | 
				
			||||||
 | 
					  font-size: 1.25rem;
 | 
				
			||||||
 | 
					  line-height: 1.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.font-bold {
 | 
					.font-bold {
 | 
				
			||||||
  font-weight: 700;
 | 
					  font-weight: 700;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.italic {
 | 
				
			||||||
 | 
					  font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.text-black {
 | 
					.text-black {
 | 
				
			||||||
  --tw-text-opacity: 1;
 | 
					  --tw-text-opacity: 1;
 | 
				
			||||||
  color: rgb(0 0 0 / var(--tw-text-opacity));
 | 
					  color: rgb(0 0 0 / var(--tw-text-opacity));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,22 @@
 | 
				
			||||||
<div class="p-2 w-full border border-black">
 | 
					<div class="w-full flex flex-col">
 | 
				
			||||||
    <p class="text-2xl font-mono">{{name}} <span class="text-sm">{{weight}}</span></p>
 | 
						<div class="relative pl-2">
 | 
				
			||||||
 | 
							{% if has_parent %}
 | 
				
			||||||
 | 
								<div class="absolute top-4 left-[calc(-1rem-1px)] -translate-y-1/2 w-4 h-4 bg-white border-4 border-black radius-full"></div>
 | 
				
			||||||
 | 
							{% endif %}
 | 
				
			||||||
 | 
							<span class="text-2xl font-mono">{{name}} <span class="text-sm">{{weight}}</span></span>
 | 
				
			||||||
		<p>{{summary}}</p>
 | 
							<p>{{summary}}</p>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ul class="mt-2">
 | 
						{% if dependencies != empty %}
 | 
				
			||||||
 | 
							<ul class="flex flex-col gap-y-1 pl-2 ml-8 border-l-2 border-black pt-4">
 | 
				
			||||||
			{% for dep in dependencies %}
 | 
								{% for dep in dependencies %}
 | 
				
			||||||
            <form class="p-2 w-full border border-black" hx-post="/api/dependency" hx-swap="outerHTML" hx-trigger="load delay:100ms">
 | 
									<form class="p-2 w-full border border-black" hx-post="/api/dependency-tree" hx-swap="outerHTML" hx-trigger="load">
 | 
				
			||||||
					<input type="hidden" name="package" value="{{dep.name}}"/>
 | 
										<input type="hidden" name="package" value="{{dep.name}}"/>
 | 
				
			||||||
					<p class="text-2xl font-mono">{{dep.name}}</p>
 | 
										<p class="text-2xl font-mono">{{dep.name}}</p>
 | 
				
			||||||
				</form>
 | 
									</form>
 | 
				
			||||||
			{% endfor %}
 | 
								{% endfor %}
 | 
				
			||||||
		</ul>
 | 
							</ul>
 | 
				
			||||||
 | 
						{% else %}
 | 
				
			||||||
 | 
							<p class="pl-2">No dependencies</p>
 | 
				
			||||||
 | 
						{% endif %}
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					{% layout 'layout.liquid' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<script src="/static/lib/sigma.min.js"></script>
 | 
				
			||||||
 | 
					<script src="/static/lib/graphology.umd.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<title>Depyth</title>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<main class="p-4 flex-1 flex flex-col container mx-auto">
 | 
				
			||||||
 | 
							<header class="w-fit mx-auto mb-8">
 | 
				
			||||||
 | 
								<h1 class="text-5xl my-4 text-center">Depyth</h1>
 | 
				
			||||||
 | 
							</header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<h2 class="text-4xl my-3">Analysis of your requirements.txt</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<h3 class="text-3xl my-3">General</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<p>Installing your requirements.txt would mean installing up to <span class="font-bold">{{ nb_deps }}</span> packages, for a total download size of around <span class="font-bold">{{totalWeight.value}}</span>.</p>
 | 
				
			||||||
 | 
						<p>Note: this estimation does not take into account platforms and architectures, and includes all optional packages.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<h3 class="text-3xl my-3">Weight chart</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div class="grid grid-cols-[auto_1fr]">
 | 
				
			||||||
 | 
							{% for package in packageByWeight %}
 | 
				
			||||||
 | 
							<div class="flex flex-col justify-center h-16">
 | 
				
			||||||
 | 
								<p class="w-fit text-xl font-bold">{{package.name}}</p>
 | 
				
			||||||
 | 
								<p>{{package.humanWeight}} - {{ package.weight | divided_by: totalWeight.raw | times: 100 | round }}%</p>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<progress value="{{ package.weight }}" max="{{ totalWeight.raw }}" min="0" class="w-full h-16"></progress>
 | 
				
			||||||
 | 
						{% endfor %}
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<h3 class="text-3xl my-3">Dependency graph</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div id="graph" class="w-full h-[60vh] bg-white border border-black"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<script>
 | 
				
			||||||
 | 
							const graph = new graphology.Graph();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							graph.import( JSON.parse(`{{ graph | json }}`) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Instantiate sigma.js and render the graph
 | 
				
			||||||
 | 
							const sigmaInstance = new Sigma(graph, document.getElementById("graph"));
 | 
				
			||||||
 | 
						</script>
 | 
				
			||||||
 | 
					</main>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					{% layout 'layout.liquid' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<script src="/static/lib/sigma.min.js"></script>
 | 
				
			||||||
 | 
					<script src="/static/lib/graphology.umd.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<title>Depyth</title>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<main class="p-4 mx-auto flex-1 max-w-[800px]">
 | 
				
			||||||
 | 
						<header class="w-fit mx-auto mb-8">
 | 
				
			||||||
 | 
							<h1 class="text-5xl my-4 text-center">Depyth</h1>
 | 
				
			||||||
 | 
							<p class="italic text-center">Dumb python packages things</p>
 | 
				
			||||||
 | 
						</header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<h2 class="text-4xl my-3">{{packagename}}</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<h3 class="text-3xl my-2">General info</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<p>Installing this package means installing up to <span id="total-packages-installed" class="font-bold">0</span> packages</p>
 | 
				
			||||||
 | 
						<p>Installing this package means installing up to <span id="total-weight" class="font-bold">0</span><span id="total-weight-unit">kb</span>.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<h3 class="text-3xl my-2">Dependency graph</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div id="graph" class="w-full h-[600px] bg-white border border-black"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<script>
 | 
				
			||||||
 | 
							// Create a graphology graph
 | 
				
			||||||
 | 
							fetch("/api/graph/{{packagename}}").then( res => res.json() ).then( json => {
 | 
				
			||||||
 | 
								console.log(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								document.getElementById('total-packages-installed').innerText = '?';
 | 
				
			||||||
 | 
								document.getElementById('total-weight').innerText = json.weight;
 | 
				
			||||||
 | 
								document.getElementById('total-weight-unit').innerText = json.weightUnit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const graph = new graphology.Graph();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								graph.import( json.graph );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Instantiate sigma.js and render the graph
 | 
				
			||||||
 | 
								const sigmaInstance = new Sigma(graph, document.getElementById("graph"));
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</main>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,41 @@
 | 
				
			||||||
{% layout 'layout.liquid' %}
 | 
					{% layout 'layout.liquid' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block head %}
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<script src="/static/lib/sigma.min.js"></script>
 | 
				
			||||||
 | 
					<script src="/static/lib/graphology.umd.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<title>Depyth</title>
 | 
					<title>Depyth</title>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
<main class="p-4 mx-auto flex-1 max-w-[800px]">
 | 
					<main class="p-4 mx-auto flex-1 max-w-[800px]" id="main">
 | 
				
			||||||
    <h1 class="text-5xl my-4">Depyth</h1>
 | 
						<header class="w-fit mx-auto mb-8">
 | 
				
			||||||
 | 
							<h1 class="text-5xl my-4 text-center">Depyth</h1>
 | 
				
			||||||
 | 
							<p class="italic text-center">Dumb python packages thing</p>
 | 
				
			||||||
 | 
						</header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form hx-post="/api/dependency" hx-target="#displaybox" hx-swap="innerHTML" class="flex flex-col">
 | 
						<h2 class="text-4xl my-3">Inspect a requirements.txt file</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<form hx-post="/api/upload-requirements" class="flex flex-col" id="requirements-form" hx-encoding="multipart/form-data" hx-target="#main" hx-swap="outerHTML">
 | 
				
			||||||
 | 
							<div class="flex">
 | 
				
			||||||
 | 
								<input type='file' name='requirementsFile' class="flex-1 border-2 border-black">
 | 
				
			||||||
 | 
								<button class="h-full w-fit px-2 py-1 bg-cyan-700 text-white flex border-2 border-black">
 | 
				
			||||||
 | 
									Upload
 | 
				
			||||||
 | 
								</button>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<div class="htmx-indicator flex flex-col w-full min-h-72 items-center justify-center">
 | 
				
			||||||
 | 
								<img src="/static/bars.svg" width="64" height="64"/> 
 | 
				
			||||||
 | 
								<p class="text-center">Analysing your (quite bloated) requirements...</p>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{% if false %}
 | 
				
			||||||
 | 
							<h2 class="text-4xl my-3">Inspect a package</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<form hx-post="/api/dependency-tree-non-infinite" hx-target="#noinfinite-displaybox" hx-swap="innerHTML" class="flex flex-col">
 | 
				
			||||||
 | 
								<input type="hidden" name="isfirst" value="true"/>
 | 
				
			||||||
			<label for="package-name-input" class="block my-1">Enter the name of the package to inspect</label>
 | 
								<label for="package-name-input" class="block my-1">Enter the name of the package to inspect</label>
 | 
				
			||||||
			<div class="flex h-min w-full">
 | 
								<div class="flex h-min w-full">
 | 
				
			||||||
				<input
 | 
									<input
 | 
				
			||||||
| 
						 | 
					@ -23,23 +50,10 @@
 | 
				
			||||||
						<button class="h-full w-fit px-2 py-1 bg-cyan-700 text-white flex border-2 border-black">submit</button>
 | 
											<button class="h-full w-fit px-2 py-1 bg-cyan-700 text-white flex border-2 border-black">submit</button>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</form>
 | 
							</form>
 | 
				
			||||||
 | 
						{% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="flex flex-col w-full my-8" id="displaybox">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <!--<div class="p-2 w-full border border-black">
 | 
					 | 
				
			||||||
            <p class="text-2xl font-mono">torch <span class="text-sm">158kb</span></p>
 | 
					 | 
				
			||||||
            <p>Tensors and Dynamic neural networks in Python with strong GPU acceleration.</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ul class="mt-2">
 | 
					 | 
				
			||||||
                <div class="p-2 w-full border border-black">
 | 
					 | 
				
			||||||
                    <p class="text-2xl font-mono">filelock <span class="text-sm">33kb</span></p>
 | 
					 | 
				
			||||||
                    <p>A platform independent file lock.</p>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </ul>
 | 
					 | 
				
			||||||
        </div>-->
 | 
					 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</main>
 | 
					</main>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <link href="/static/styles.css" rel="stylesheet">
 | 
					    <link href="/static/styles.css" rel="stylesheet">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <script src="/static/htmx.min.js" defer></script>
 | 
					    <script src="/static/lib/htmx.min.js" defer></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {% block head %}{% endblock %}
 | 
					    {% block head %}{% endblock %}
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue