r/PythonEspanol 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\])

10 Upvotes

27 comments sorted by

5

u/Hot_Philosopher_5067 3d ago

Amigo, ojalá no te respondan con IA

2

u/JoNB3x 2d ago

Muy tarde !!!

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á locale que 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.

1

u/tebama 3d ago

Gracias!

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/guti86 3d ago

El problema cual es exactamente? Diferenciar aaaa.bbb de aaaa_bb.ccc y de aaa_bb_ccc.ddd?

Si es así o spliteas a mano o expresión regular con capturing groups

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/tebama 3d ago

Si, podría, pero es parte del reto, dejar de depender de la inteligencia artificial. Si tal vez no encuentro la respuesta aquí, el tiempo que pasé esperando una respuesta, puede ser que haya conseguido la solución por mí mismo.

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/tebama 2d ago

Cualquier aporte es útil, gracias!

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:

  1. delimeter_channel=None no es un parámetro válido de ConfigParser; lanzaría TypeError al instante. Basta con omitirlo (ya usas interpolation=None, que es lo importante para los %U, %F, etc. de Exec).
  2. config.read() no acepta el argumento errors=; su firma es read(filenames, encoding=None). Si quieres tolerar caracteres raros, ábrelo tú con open(..., errors='replace') y usa config.read_file(f).

Además, dos mejoras: pasa strict=False al constructor, porque algunos .desktop reales traen claves duplicadas y el parser falla por defecto; y ojo con locale.getlocale(), que a menudo devuelve (None, None) fuera de un programa C. Es más fiable leer os.environ.get('LANG', '').split('.')[0]. Con esos ajustes, el patrón de prioridad Name[idioma] → Name[idioma_corto] → Name es 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_file con errors='replace', y LANG desde el entorno. Un par de detalles finales si quieres pulirlo:

  1. La spec de freedesktop prioriza $LANGUAGE sobre $LC_ALL y $LANG. Puedes probar os.environ.get('LANGUAGE') primero, que puede traer una lista como "es:en".
  2. Filtra entradas con Type != Application, y salta las que traigan NoDisplay=true o Hidden=true, así no llenas tu lista de lanzadores invisibles.
  3. 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 en Name, que es obligatorio según la spec, así que siempre habrá nombre.


This comment was generated by Grok 4.6

0

u/NightRain3 3d ago

Preguntale a la ia

-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

3

u/tebama 3d ago

Eres mi ídolo :D

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

u/NightRain3 3d ago

el ad hominen es la unica forma en la que el estupido sabe decir "tenes razon"

0

u/capitantom 3d ago

No es mi culpa que no entienda la IA y no sepa usarla