saber: lo instalado en la maquina y los docs de repos clonados entran al indice
instalado.py lee los .desktop y los paquetes para saber que hay en el equipo y para que sirve cada cosa. indexa.py lo engancha, y con el los docs markdown de las herramientas clonadas en CODERS. Las dos fuentes son personales: se quedan en el indice de la maquina y no entran en el publico. busca_cli.py expone el mismo motor que usa el cerebro (saber.busca) para consultar el indice desde la terminal, con --n y --json. reentrena_noche.sh encadena las fases del reentrenamiento largo y las deja corriendo horas; cada fase es reanudable por su cuenta. eval del indice vivo: hit@1 43/57 (75%), hit@5 55/57 (96%), sim 0.909.
This commit is contained in:
parent
b1f8118272
commit
7c5381eea7
7 changed files with 369 additions and 2 deletions
140
nucleo/saber/instalado.py
Executable file
140
nucleo/saber/instalado.py
Executable file
|
|
@ -0,0 +1,140 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Indexa las aplicaciones instaladas en ESTA maquina: que hay y para que sirve.
|
||||
|
||||
./instalado.py di cuantas entradas saldrian
|
||||
./instalado.py --volcado imprime unas cuantas de muestra
|
||||
|
||||
Dos fuentes, ambas locales:
|
||||
1. Los .desktop (/usr/share/applications, ~/.local, flatpak): las apps que el
|
||||
usuario ve y lanza, con su nombre, para que sirven (Comment) y el comando.
|
||||
2. Los paquetes que el usuario instalo a mano (apt-mark showmanual), con su
|
||||
descripcion de una linea. No las dependencias: eso es ruido.
|
||||
|
||||
Es privado por definicion —dice que software tiene esta maquina—, asi que
|
||||
indexa.py lo mete SOLO en el indice personal, nunca en el publico ni en gitea.
|
||||
"""
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
DIRS_DESKTOP = [
|
||||
"/usr/share/applications",
|
||||
os.path.expanduser("~/.local/share/applications"),
|
||||
"/var/lib/flatpak/exports/share/applications",
|
||||
os.path.expanduser("~/.local/share/flatpak/exports/share/applications"),
|
||||
"/var/lib/snapd/desktop/applications",
|
||||
]
|
||||
|
||||
|
||||
def _campo(texto, clave):
|
||||
m = re.search(r"^%s=(.+)$" % re.escape(clave), texto, re.M)
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
|
||||
def _apps_desktop():
|
||||
vistos = set()
|
||||
for d in DIRS_DESKTOP:
|
||||
for ruta in sorted(glob.glob(os.path.join(d, "*.desktop"))):
|
||||
base = os.path.basename(ruta)
|
||||
if base in vistos:
|
||||
continue
|
||||
vistos.add(base)
|
||||
try:
|
||||
with open(ruta, encoding="utf-8", errors="ignore") as f:
|
||||
txt = f.read()
|
||||
except OSError:
|
||||
continue
|
||||
if _campo(txt, "NoDisplay").lower() == "true":
|
||||
continue
|
||||
nombre = _campo(txt, "Name")
|
||||
if not nombre:
|
||||
continue
|
||||
comentario = _campo(txt, "Comment") or _campo(txt, "GenericName")
|
||||
categorias = _campo(txt, "Categories").replace(";", ", ").strip(", ")
|
||||
exe = re.sub(r"%[a-zA-Z]", "", _campo(txt, "Exec")).strip()
|
||||
cmd = exe.split()[0] if exe else ""
|
||||
partes = [f"{nombre}"]
|
||||
if comentario:
|
||||
partes.append(f"— {comentario}")
|
||||
if cmd:
|
||||
partes.append(f"(se lanza con: {cmd})")
|
||||
if categorias:
|
||||
partes.append(f"[{categorias}]")
|
||||
yield {
|
||||
"tipo": "instalado", "herramienta": nombre, "perfil": "SISTEMA",
|
||||
"publico": False, "tema": categorias.lower(),
|
||||
"fichero": f"app instalada: {base}",
|
||||
"texto": "Aplicacion instalada: " + " ".join(partes),
|
||||
}
|
||||
|
||||
|
||||
def _paquetes_manuales():
|
||||
"""Los paquetes apt que instalo el usuario (no dependencias), con su resumen."""
|
||||
try:
|
||||
man = subprocess.run(["apt-mark", "showmanual"], capture_output=True,
|
||||
text=True, timeout=30).stdout.split()
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return
|
||||
if not man:
|
||||
return
|
||||
# resumen de cada uno en un solo dpkg-query (rapido)
|
||||
try:
|
||||
salida = subprocess.run(
|
||||
["dpkg-query", "-W", "-f=${Package}\t${binary:Summary}\n", *man],
|
||||
capture_output=True, text=True, timeout=60).stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return
|
||||
lineas = []
|
||||
for l in salida.splitlines():
|
||||
if "\t" not in l:
|
||||
continue
|
||||
pkg, resumen = l.split("\t", 1)
|
||||
if resumen.strip():
|
||||
lineas.append(f"{pkg}: {resumen.strip()}")
|
||||
# se agrupan en fragmentos (no una entrada por paquete: son cientos)
|
||||
buffer = "Paquetes que el usuario instalo a mano en esta maquina:\n"
|
||||
for ln in lineas:
|
||||
if len(buffer) + len(ln) > 1100 and buffer:
|
||||
yield _pkg_entry(buffer)
|
||||
buffer = "Paquetes instalados (cont.):\n"
|
||||
buffer += "- " + ln + "\n"
|
||||
if buffer.strip():
|
||||
yield _pkg_entry(buffer)
|
||||
|
||||
|
||||
def _pkg_entry(texto):
|
||||
return {"tipo": "instalado", "herramienta": "paquetes apt", "perfil": "SISTEMA",
|
||||
"publico": False, "tema": "paquetes", "fichero": "apt-mark showmanual",
|
||||
"texto": texto.strip()}
|
||||
|
||||
|
||||
def entradas():
|
||||
yield from _apps_desktop()
|
||||
yield from _paquetes_manuales()
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--volcado", action="store_true")
|
||||
a = p.parse_args()
|
||||
apps = pkgs = 0
|
||||
muestras = []
|
||||
for e in entradas():
|
||||
if e["herramienta"] == "paquetes apt":
|
||||
pkgs += 1
|
||||
else:
|
||||
apps += 1
|
||||
if len(muestras) < 6:
|
||||
muestras.append(e)
|
||||
print(f" {apps} apps (.desktop) + {pkgs} fragmentos de paquetes apt")
|
||||
if a.volcado:
|
||||
for e in muestras:
|
||||
print(" -", e["texto"][:160].replace("\n", " "))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue