Skip to content

Instantly share code, notes, and snippets.

@diegovgsilva95
Created January 29, 2025 03:52
Show Gist options
  • Select an option

  • Save diegovgsilva95/e80672585abc842d00226d5cf71073ba to your computer and use it in GitHub Desktop.

Select an option

Save diegovgsilva95/e80672585abc842d00226d5cf71073ba to your computer and use it in GitHub Desktop.
Node.js - Parsing and sorting Pacman (Arch, btw) packages by decreasing size, together with their install reason (e.g., explicitly installed, installed as a dependency)
// Intended usage:
// $ pacman -Qi | node index.mjs
// I know there are (plenty of) existing solutions to this. I just made my own.
import { stdin, stdout } from "process"
import { Console, log } from "console"
import { createWriteStream, writeFileSync } from "fs"
import { writeFile } from "fs/promises"
const sleep = ms => new Promise(r => setTimeout(r, ms))
// Historical debugging purposes... Keeping as a comment so I can query my own code in the future.
// let auxConsole = new Console({
// colorMode: true,
// stdout: createWriteStream(null, {fd: 3})
// })
// I chose to keep comments from my TDD approach.
let packages = {}
let reminiscent = ""
let endIsNigh = false
let timer = null
const parsePackage = function(packageStr){
let name, size, reason
let props = [...packageStr.trim().matchAll(/^(?<key>[^\:]+):(?<val>.+)$/gm)]
for(let {groups: {key, val}} of props){
key = key.trim()
val = val.trim()
if(key == "Name")
name = val
else if(key.includes("Size"))
size = val
else if(key.includes("Reason"))
reason = val
}
if(typeof name !== "string" || typeof size !== "string" || typeof reason !== "string"){
throw new TypeError(`Detected rogue string!`)
}
packages[name] = {name, size, reason}
// auxConsole.log(name)
return name
}
const sizeScales = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]
const parseSize = function(size){
let cmavs = size?.match(/(?<value>[^ ]+) (?<scale>[A-Z]+)/i)
if(!cmavs)
throw new TypeError("Not a size!")
let {value, scale} = cmavs.groups
let scaleExp = sizeScales.indexOf(scale)
value = parseFloat(value)
return {value, scaleExp}
}
const compareSizes = function(sizeA, sizeB){
let parsedSizeA = parseSize(sizeA)
let parsedSizeB = parseSize(sizeB)
return parsedSizeA.scaleExp == parsedSizeB.scaleExp ? Math.sign(parsedSizeA.value-parsedSizeB.value) : Math.sign(parsedSizeA.scaleExp - parsedSizeB.scaleExp)
}
const onFinished = async function(){
if(reminiscent.length > 0){
log("Parsing remaining block of package info...")
parsePackage(reminiscent)
}
log("Reached the finish with " + Object.keys(packages).length + " packages computed")
log("Largest packages:")
let sortedPackages = Object.entries(packages).sort((a,b)=>compareSizes(b[1].size, a[1].size)).map(([_,info])=>`${info.name} (${info.size}): ${info.reason}`)
log(sortedPackages.map(line=>` - ${line}`).slice(0,10).join("\n"))
await writeFile("sorted-package-list.lst", sortedPackages.join("\n"))
}
stdin.on("data", async function(data){
// stdin.pause()
let dataStr = data.toString("utf-8")
// log("Received", dataStr.length)
if(dataStr.length > 0 && dataStr.includes("\n\n")){
let doubleMatches = [...dataStr.matchAll(/\n\n/g)]
log(`Parsing ${doubleMatches.length} blocks of package info...`)
// log("What about the reminiscent?", reminiscent.length, reminiscent.length>0?`<"${reminiscent.slice(0, 5)}...${reminiscent.slice(-5)}">`:"")
let lastOffset = 0
let i = 0
for(let match of doubleMatches){
// if((iter == 0 && (++i == doubleMatches.length)) || (iter == 1 && ++i == 1)){
// log(`Parsing from ${lastOffset} to ${match.index}...`)
parsePackage(reminiscent + dataStr.slice(lastOffset, match.index))
if(reminiscent.length>0) reminiscent = ""
// log(`[${}]`)
// }
lastOffset = match.index+match[0].length
// await sleep(10)
}
if(dataStr.length > lastOffset){
// log(`last offset = ${lastOffset}, len = ${dataStr.length}, setting reminiscent`)
reminiscent = dataStr.slice(lastOffset, dataStr.length)
}
} else {
log("WARN: Got reminiscent without another reminiscent, trying to parse them...")
// Should I try to detect and break double lines here?
// Bc the reminiscent block isn't expected to be so large so to contain info from multiple packages.
log({reminiscent, dataStr})
parsePackage(reminiscent + dataStr)
}
// await sleep(50)
if(endIsNigh){
timer && clearTimeout(timer)
onFinished()
}
// else
// stdin.resume()
})
stdin.on("end", function(){
endIsNigh = true // Beware the end, for the end is nigh!
if(stdin.readableFlowing)
timer = setTimeout(onFinished, 100)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment