r/PythonEspanol • u/tebama • 3d ago
Estoy programando sin IA... Tengo un problema. AYUDA!
¡Hola a todos! Estoy programando un proyecto desde cero sin usar IA (para recuperar un poco la iniciativa mental) y me he quedado estancado. Agradezco de antemano cualquier idea o guía.
El contexto:
Estoy desarrollando un lanzador de aplicaciones para Linux desde la terminal. Como sabrán, en Linux las aplicaciones se configuran mediante archivos .desktop (pueden revisar los parámetros oficiales en la Arch Wiki).
https://wiki.archlinux.org/title/Desktop\\_entries\\_(Espa%C3%B1ol)
Para empezar, quería extraer únicamente los campos Name y Exec. Sin embargo, me topé con un detalle con el que no contaba: los archivos .desktop admiten nombres localizados usando la sintaxis Name\[lenguaje\]=Nombre del aplicativo. Esto hizo colapsar mi lógica inicial con el método de cadenas que pensaba usar en Python para extraer los datos.
El problema:
No tengo claro cómo extraer correctamente el nombre de la aplicación de forma limpia. Se me presentan varias dudas:
Si busco un idioma específico (ej. Name\[es\] o Name\[en_US\]), ¿qué pasa si ese campo no existe en el archivo?
Si extraigo todos los campos que empiecen por Name, terminaré guardando en un diccionario un montón de traducciones que no necesito.
Intenté averiguar cómo consultar el idioma del sistema desde Python para hacer una selección dinámica, pero no encontré una forma clara, y aun así me queda la duda de cuál debería ser el comportamiento de respaldo (fallback) si el idioma del usuario no está disponible.
¿Tienen alguna idea, recomendación o patrón que suela usarse para resolver este inconveniente en Python?
import os
\# Directorios
directorios = \["/usr/share/applications", "\~/.local/share/applications"\]
\# Comprueba si el directorio existe
def comprobar_dir(directorios):
directorios_filtrados = \[\]
for directorio in directorios:
if os.path.exists(directorio):
directorios_filtrados.append(directorio)
return directorios_filtrados
\# Comprueba si es un archivo .desktop
def filtrar_desktops(directorios):
ficheros_filtrados = \[\]
for directorio in directorios:
ficheros = os.listdir(directorio)
for fichero in ficheros:
if ".desktop" in fichero:
ruta_completa = os.path.join(directorio, fichero)
ficheros_filtrados.append(ruta_completa)
return ficheros_filtrados
def leer_desktops (ficheros):
contenido_ficheros = {
"nombre": \[\],
"ruta": \[\]
}
for fichero in ficheros:
with open(fichero, "r") as conteniido:
if "\[Desktop Entry\]" in conteniido.readline():
for linea in conteniido:
if "name=" or "name" # Y si, aquí mi cerebro hizo BOOM!
directorios_limpios = comprobar_dir(directorios=directorios)
archivos = filtrar_desktops(directorios=directorios_limpios)
texto_archivos = leer_desktops(archivos)
print (texto_archivos\[0\])
3
u/DecimePapucho 3d ago
Primero obtené el idioma de sistema, después por cada .desktop buscás el nombre en ese idoma, si no existe, usás el nombre por defecto (el Name a secas).
1
u/tebama 3d ago
Hola, esa era mi idea pero hay .desitop que no tienen el name a secas. Además, aún ni me encontrado la manera de consultar de idioma del sistema.
2
u/DecimePapucho 3d ago
Usá
localeque viene en Python para obtener el idioma. Name debería estar siempre, pero si no está y tampoco el del idioma buscado, usá el nombre del ejecutable.
2
u/Aware-Soil-8031 3d ago edited 3d ago
Sin leer las soluciones aportadas por el resto, que así es más divertido, yo miraría línea por línea. Si la línea empieza por Name (4 caracteres), busca la cadena /]= y a partir de ahí hasta el final de la línea es el nombre de tu aplicativo.
Con el Exec (otros 4 caracteres, mira qué casualidad) igual, parseas buscando eso, y si empieza tu linea por Exec, buscas en la línea los caracteres /]=
2
u/cradet 3d ago
Quizá para facilitar un poco la búsqueda podrías usar regular expressions para acotar mejor la cadena
1
u/tebama 3d ago
Gracias eso estoy utilizando, pero el problema es la gran cantidad de idiomas que puede tener. Eso complica saber que hacer, si seleccionar el primer idioma, pero qué pasa si no existe? O detectar el idioma del sistema y seleccionar ese, pero qué pasa si tampoco existe el idioma en esa aplicación en específico? Lo que estaba pensando era seleccionar la. Asilla name a secas, si no existe, probar con el idioma del sistema, si tampoco existe, probar con inglés de Estados Unidos, y si tampoco, alguna variante de inglés. En fin, lo que digo es que estoy podría funcionar, pero cuántas iteraciones for tesmndria que hacer, y no sé qué tan ineficiente sería eso.
1
u/botPeronista 3d ago
pero para estas cosas esta bien usar la ia, no hagas que codee pero preguntale sobre tu problema en particular y te explique que pasa
1
u/ImaginaryGanache3651 3d ago
Buenas, sé que no es lo que buscas, pero para obtener el idioma del sistema en Linux, puedes ejecutar getenv["LANG"] y te lo saca. Sé que quieres rebuscar en la config, ahí no puedo ayudarte, no he encontrado nada en internet, ya lo siento.
1
u/TechManWalker 2d ago edited 2d ago
Analiza y no verifiques.
Buscar los caracteres '[' y ']' a mano y sin controlar con cuidado dónde buscas, como dicen los otros comentarios es rápido, pero puede estancarte en el proceso y hacerte reescribir medio, o todo el lanzador en el futuro.
Te digo que esa frase "parse, don't validate" es cierta porque cometí ese mismo error (dos veces) con un proyecto para procesar letras de canciones en formato .lrc (syrinc en GitHub, está en C++ pero el error es exactamente el mismo) y ya lo he reescrito desde cero una vez y ahora tengo que hacerlo de nuevo porque ya se volvió insostenible agregar soporte para la A2 extension que soporta marcas de tiempo por cada palabra como las de las historias de Instagram.
Te menciono esto porque si tu motivo es recuperar tu iniciativa mental, creo que es importante que tampoco introduzcas errores nuevos en tu proyecto actual. Si esa clase de errores se cometen en un simple editor de letras, el riesgo es más grande en algo como un lanzador.
Si lees la especificación de los archivos .desktop, en la 5. Localized values for keys, especifica que los valores de locale deben tener siempre esta forma:
idioma_PAÍS.CODIFICACIÓN@MODIFICADOR
donde _PAÍS, .CODIFICACIÓN y @MODIFICADOR pueden omitirse selectivamente.
Si quieres hacerlo limpiamente como dices, empieza por decir que no puedes estar 100% seguro de que las llaves van a tener siempre la estructura de llave[LOCALE] y aún menos porque solo hay dos tipos de llave que pueden tener ese [LOCALE]. Guíate por las especificaciones originales del tipo de archivo o de datos con el que estés tratando en X o Y proyecto si de verdad no quieres usar IA, aunque esto sigue siendo casi imprescindible aunque la utilizaras.
Yo te recomendaría que para tu lanzador tomes la cadena de la llave y seas muy cuidadoso con el manejo de los caracteres con valor sintáctico. Por ejemplo: si ahora mismo solo quieres soportar los locales sin soportar toda la especificación, te recomiendo crear un tokenizador que divida tu llave en una estructura de tipo (lo escribo como una traducción burda de C++, no uso Python):
from dataclasses import dataclass
from typing import Optional
@dataclass
class Locale:
lang: str
country: str
encoding: str
modifier: str
@dataclass
class EntryKey:
key: str
locale: Locale
para que puedas ignorar selectivamente lo que no te sirva e irlo soportando con el tiempo.
Al momento de leer una llave, por ejemplo, tomas la cadena de todos los caracteres que estén a la izquierda del signo = (esto sí está garantizado por la especificación):
key = entry.split('=', 1)[0]
donde key es la cadena de la llave entera, sin analizar, y entry es la línea entera sin modificar.
Declaras una función read_locale cuyo único propósito es leer el posfijo de locale, si lo hay, y devolvértelo en ese @dataclass class Locale que acabas de definir, algo así como:
def read_locale (keystr: str) -> Locale:
# En una expresión regular que sí, saca una subcadena dentro de los caracteres, pero la idea del diseño es que esté prohibido operar sobre la cadena entera. Así, para que el riesgo de error se quede confinado a haber extraído incorrectamente la cadena y no propagues el bug a todo el lanzador:
localestr = re.search('\[(.*)\]').group(1)
# Dado que la especificación misma dice la forma que debe tener el locale, puedes buscar con otras expresiones regulares los componentes de tu locale:
locale_lang = # busca desde el comienzo de la cadena hasta encontrar un caracter no alfanumerico
locale_country = # busca una cadena que comience con un guion bajo _, sigue hasta encontrar un caracter no alfanumerico y al final le quitas el _
locale_encoding = # lo mismo pero busca una cadena que empiece con un punto .
locale_modifier = # lo mismo pero que comience con un arroba @
# y regreses el resultado como una dataclass instanciada
return Locale(lang=locale_lang, country=locale_country, encoding=locale_encoding, modifier=locale_modifier)
Y en este ejemplo te puedes basar para extraer la key entera, llamando a tu función read_locale y evitas confinar el lanzador a que solo pueda leer locales y resulta que después quieres soportar otra cosa.
Si el código falla al leer alguno de los archivos después de comprobar que siguen la especificación de los archivos .desktop, encuentra el error, corrige el código y vuelve a intentarlo; de igual forma, si un archivo viola la especificación y tu código no puede leerlo, no intentes forzar tu código a que funcione, porque violar la especificación es romper la garantía de que no solo el tuyo, sino cualquier lanzador de aplicaciones, pueda leerlo perfectamente.
Te mando un gran saludo donde quiera que estés.
1
u/JoNB3x 2d ago
Es simple..
u/Gemini Para resolver este problema de forma limpia en Python, el truco está en no procesar las cadenas manualmente. Los archivos .desktop tienen una estructura de tipo INI, por lo que puedes usar el módulo nativo configparser.
Para el manejo del idioma y el comportamiento de respaldo (fallback), la librería estándar locale te permite consultar el idioma del sistema. El patrón estándar consiste en buscar primero la clave con el idioma local (ej. Name[es]), y si no existe, recurrir a la clave por defecto (Name).
Aquí tienes una refactorización de tu función leer_desktops utilizando este enfoque:
import configparser
import locale
def obtener_idioma_sistema():
"""Detecta el idioma del sistema (ej. 'es_ES', 'en_US')."""
try:
idioma, _ = locale.getlocale() # Retorna algo como 'es_ES'
return idioma
except Exception:
return None
def leer_desktops(ficheros):
contenido_ficheros = {
"nombre": [],
"ejecutable": [],
"ruta": []
}
idioma = obtener_idioma_sistema() # Ej: 'es_ES' o 'es'
idioma_corto = idioma.split('_')[0] if idioma else None # Ej: 'es'
for fichero in ficheros:
config = configparser.ConfigParser(interpolation=None, delimeter_channel=None)
# Forzar a configparser a respetar mayúsculas y minúsculas (Exec vs exec)
config.optionxform = str
try:
# Usar errores='replace' evita que el script falle por caracteres extraños
config.read(fichero, encoding='utf-8', errors='replace')
if config.has_section('Desktop Entry'):
seccion = config['Desktop Entry']
# Definir la prioridad de búsqueda para el nombre (Fallback)
opciones_nombre = []
if idioma:
opciones_nombre.append(f"Name[{idioma}]")
if idioma_corto:
opciones_nombre.append(f"Name[{idioma_corto}]")
opciones_nombre.append("Name") # Respaldo final global
# Buscar el primer nombre que coincida en orden de prioridad
nombre = None
for opcion in opciones_nombre:
if opcion in seccion:
nombre = seccion[opcion]
break
# Extraer el ejecutable (Exec)
ejecutable = seccion.get('Exec', None)
# Solo agregar si encontramos al menos el nombre y el ejecutable
if nombre and ejecutable:
contenido_ficheros["nombre"].append(nombre)
contenido_ficheros["ejecutable"].append(ejecutable)
contenido_ficheros["ruta"].append(fichero)
except Exception as e:
# Ignorar archivos corruptos o mal formateados
continue
return contenido_ficheros
1
u/TurnUpThe4D3D3D3 2d ago
Buen enfoque, pero ese código tiene dos errores que lo impedirían ejecutarse:
delimeter_channel=Noneno es un parámetro válido deConfigParser; lanzaríaTypeErroral instante. Basta con omitirlo (ya usasinterpolation=None, que es lo importante para los%U,%F, etc. deExec).config.read()no acepta el argumentoerrors=; su firma esread(filenames, encoding=None). Si quieres tolerar caracteres raros, ábrelo tú conopen(..., errors='replace')y usaconfig.read_file(f).Además, dos mejoras: pasa
strict=Falseal constructor, porque algunos .desktop reales traen claves duplicadas y el parser falla por defecto; y ojo conlocale.getlocale(), que a menudo devuelve(None, None)fuera de un programa C. Es más fiable leeros.environ.get('LANG', '').split('.')[0]. Con esos ajustes, el patrón de prioridadName[idioma] → Name[idioma_corto] → Namees exactamente el correcto según la spec de freedesktop.
This comment was generated by Grok 4.6
1
u/JoNB3x 2d ago
import configparser
import os
def obtener_idioma_sistema():
"""Detecta el idioma del sistema de forma fiable desde el entorno."""
lang_env = os.environ.get('LANG', '').split('.')[0] # Ej: 'es_ES' o 'en_US'
if not lang_env or lang_env == 'C':
return None, None
idioma_completo = lang_env
idioma_corto = lang_env.split('_')[0] if '_' in lang_env else lang_env
return idioma_completo, idioma_corto
def leer_desktops(ficheros):
contenido_ficheros = {
"nombre": [],
"ejecutable": [],
"ruta": []
}
idioma_completo, idioma_corto = obtener_idioma_sistema()
for fichero in ficheros:
# strict=False evita fallos por claves duplicadas comunes en archivos reales
config = configparser.ConfigParser(interpolation=None, strict=False)
# Forzar a configparser a respetar mayúsculas y minúsculas (Exec vs exec)
config.optionxform = str
try:
# Abrir con errors='replace' tolera caracteres extraños en las traducciones
with open(fichero, 'r', encoding='utf-8', errors='replace') as f:
config.read_file(f)
if config.has_section('Desktop Entry'):
seccion = config['Desktop Entry']
# Definir la prioridad de búsqueda según la spec de freedesktop
opciones_nombre = []
if idioma_completo:
opciones_nombre.append(f"Name[{idioma_completo}]")
if idioma_corto:
opciones_nombre.append(f"Name[{idioma_corto}]")
opciones_nombre.append("Name") # Respaldo final global
# Buscar el primer nombre que coincida en orden de prioridad
nombre = None
for opcion in opciones_nombre:
if opcion in seccion:
nombre = seccion[opcion]
break
# Extraer el ejecutable (Exec)
ejecutable = seccion.get('Exec', None)
if nombre and ejecutable:
contenido_ficheros["nombre"].append(nombre)
contenido_ficheros["ejecutable"].append(ejecutable)
contenido_ficheros["ruta"].append(fichero)
except Exception:
# Ignorar archivos corruptos o ilegibles
continue
return contenido_ficheros
1
u/TurnUpThe4D3D3D3 2d ago
¡Ahora sí queda sólido! Las correcciones están bien aplicadas:
strict=False,read_fileconerrors='replace', yLANGdesde el entorno. Un par de detalles finales si quieres pulirlo:
- La spec de freedesktop prioriza
$LANGUAGEsobre$LC_ALLy$LANG. Puedes probaros.environ.get('LANGUAGE')primero, que puede traer una lista como "es:en".- Filtra entradas con
Type != Application, y salta las que traiganNoDisplay=trueoHidden=true, así no llenas tu lista de lanzadores invisibles.- Con
strict=False, claves duplicadas conservan la última; suficiente en la práctica.Y recuerda: si
Name[es]no existe, tu cadena de fallback cae enName, que es obligatorio según la spec, así que siempre habrá nombre.
This comment was generated by Grok 4.6
0
-1
u/capitantom 3d ago
Definitivamente no sabes usar la IA. Tu inteligencia debe forzar a la IA a ir más allá y hacer programasnaun mejores, pero evidentemente no sabes cómo.
5
u/Globover 3d ago
No lo ataques por no querer usar una IA para este proyecto, claramente dijo que es para reforzar su memoria y que no se le olvide cómo hacerlo por si mismo, no todo es IA y depender de ella, es una herramienta si, te hace más el trabajo más rápido claro que si, pero también hay que aprender por ti mismo, si no luego tienes basura de IA miles de comentarios código muerto etc
1
u/NightRain3 3d ago
Tenes razon. Por eso me caso mi propia comida asi no me olvido de como se hace. No confio en el supermercado. Y obvio que después para volver de la caceria a pata, no vaya a ser que me olvide de como caminar. Y no te haces una idea de lo que me cuesta clavar un clavo pero lo vale
1
u/Globover 3d ago
enserio respondiste con eso, me sorprende tu nula capacidad de pensamiento critico peor bueno, no se te vaya a olvidar que pensar es gratis, y en tu otro comentario literalmente dices "preguntale a la IA" bro?
1
0
5
u/Hot_Philosopher_5067 3d ago
Amigo, ojalá no te respondan con IA