parent
bc1e4ab83a
commit
5b33c5f8d5
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