martes, 20 de julio de 2010

Dibujando figuras geométricas con Eukleides y LATEX

Problema 4 IMO 2007 Hanoi

Regreso después de unas vacacaciones durante las cuales la nostalgia me hizo regresar a la solución de ejercicios de las Olimpiadas Internacionales de Matemáticas. Esta vez les narraré cómo obtuve con ayuda de Eukleides la figura de uno de los problemas de geometría que resolví. La necesitaba para publicar la demostración en un nuevo blog de Soluciones de Olimpiadas, donde trataré de documentar mis experiencias en la lucha constante que ha representado la resolución de estos ejercicios tan complejos. ¡Ojalá hubiéramos tenido una herramienta como esa en mis tiempos! Espero que les sea útil a ustedes también. Me siento satisfecho puesto a que hace más de siete años que no intentaba resolverlos, así que todavía estoy en forma :o). Usted puede suscribirse a este blog si desea estar al tanto de otros temas igual de interesantes. Si lo desea también puede recibir automáticamente las respuestas a los ejercicios que se irán publicando en el nuevo blog de Soluciones de Olimpiadas. Si Usted desea también puede solicitar permisos para publicar sus propias soluciones, o artículos relacionados con estos eventos.

¿Qué es Eukleides?

Existen varias alternativas para lograr que una computadora dibuje figuras geométricas en el plano. LaTeX es una de ellas. Sin embargo, en gran parte de los casos hay que emplear herramientas diseñadas desde el punto de vista del funcionamiento interno de la computadora. En otros casos hay que especificar hasta el más mínimo detalle para obtener una figura medianamente comprensible. Todo esto dificulta el aprendizaje y la rápida familiarización de los usuarios con la herramienta, puesto a que se impone una nueva forma de pensar y a veces se necesita hasta realizar cálculos no siempre evidentes.

Eukleides es una herramienta genial que interpreta un lenguaje descriptivo para obtener representaciones de figuras de la geometría euclideana. Sus características permiten que se pueda obtener una figura conociendo solamente los conceptos matemáticos y las relaciones entre los puntos y las curvas que se mencionan en el ejercicio en cuestión. Resulta asombroso que el enunciado del problema se pueda traducir casi directamente para obtener la descripción de la figura. Utilicemos el mismo problema de geometría que resolví para ilustrar lo que estoy diciendo. Su enunciado es el siguiente :

Problema 4 - IMO 2007 (Vietnam). En un triángulo ABC la bisectriz del ángulo BCA y la circunferencia circunscrita al triángulo se cortan en R (R != C). Dicha recta corta a la mediatriz de BC en P y a la mediatriz de AC en Q . El punto medio de BC es K y el punto medio de AC es L . Demostrar que los triángulos RPK y RQL tienen áreas iguales.

A continuación representaré las condiciones del problema (resaltadas en negritas ;o) en el órden que van apareciendo en el enunciado de problema. Solo tengan en cuenta que utilizo la sintaxis de la versión 1.0.3 de eukleides (después explico más al respecto ;o)

A B C triangle

l = bisector(B, C, A)
c = circle(A, B, C)
C R intersection(l, c)
P = intersection(l, perpendicular(segment(B, C), midpoint(segment(B, C)) ))
Q = intersection(l, perpendicular(segment(A, C), midpoint(segment(A, C)) ))
K = midpoint(segment(B, C))
L = midpoint(segment(A, C))

draw(A, B, C)
draw(l)
draw(c)
draw(segment(K, P), dashed)
draw(segment(L, Q), dashed)

draw("$A$", A, -130:)
draw("$B$", B, -50:)
draw("$C$", C, 130:)
draw("$P$", P, 0.1, -160:)
draw("$Q$", Q, 0.1, 50:)
draw("$R$", R, -130:)
draw("$K$", K, 50:)
draw("$L$", L, 130:)

color(gray)
mark(A, C, R)
mark(B, C, R)

Esto es todo lo que se necesita. Como se puede apreciar el lenguaje es declarativo y resulta increíble la similitud con el enunciado del problema. También hay que resaltar que hay comandos específicos para dibujar y para denotar los puntos y rectas que aparecen en la figura. Con ellos se pueden obtener distintos estilos y formas.

Para obtener la figura incluimos en texto anterior en el fichero imo2k7pb4.euk y luego instalamos eukleides y sus dependencias (Eukleides utiliza las extensiones PSTricks de LaTeX). En Debian o Ubuntu solo está empaquetada la la versión 1.0.3 de eukleides, pero la la versión 1.5.4 de eukleides cubre las mismas necesidades con un lenguaje más expresivo. Sin embargo es posible encontrar la versión 1.5.4 empaquetada en sid, por lo que quizás pronto la tendremos en los repositorios. La instalación se haría de la siguiente manera :

$ sudo apt-get install eukleides ; XXX dependencias

Después se utiliza el comando euk2edit y se especifica el formato de salida que se desea obtener (en este caso un fichero PostScript)

$ euk2edit imo2k7pb4.euk ps

El fichero imo2k7pb4.ps contiene ahora la figura en el formato deseado. Para conocer todos los formatos que se pueden obtener, basta con leer la salida del comando

$ pstoedit -help

Conclusiones

La herramienta también permite la traducción de los comandos a varios idiomas. El fichero anterior podría ser escrito también en español (si hubiera una traducción ;o) de esta forma

A B C triangulo

l = bisectriz(B, C, A)
c = circulo(A, B, C)
C R interseccion(l, c)
P = interseccion(l, perpendicular(segmento(B, C), puntomedio(segmento(B, C)) ))
Q = interseccion(l, perpendicular(segmento(A, C), puntomedio(segmento(A, C)) ))
K = puntomedio(segmento(B, C))
L = puntomedio(segmento(A, C))

dibuja(A, B, C)
dibuja(l)
dibuja(c)
dibuja(segmento(K, P), discontinuo)
dibuja(segmento(L, Q), discontinuo)

dibuja("$A$", A, -130:)
dibuja("$B$", B, -50:)
dibuja("$C$", C, 130:)
dibuja("$P$", P, 0.1, -160:)
dibuja("$Q$", Q, 0.1, 50:)
dibuja("$R$", R, -130:)
dibuja("$K$", K, 50:)
dibuja("$L$", L, 130:)

color(gris)
resalta(A, C, R)
resalta(B, C, R)

Si consideramos que existe una herramienta visual llamada xeukleides, entonces ¡hasta un niño podría hacer esto y obtener sus figuras! . Espero que les haya gustado este artículo. Suscríbase a este blog si desea continuar este viaje para descubrir las bondades de los diferentes software libres. No olvide que si Usted desea también puede solicitar permisos para publicar sus propias demostraciones en el blog de Soluciones de Olimpiadas, o artículos relacionados con estos eventos. La invitación está hecha para recibir automáticamente las respuestas a los ejercicios que se irán publicando allí.

viernes, 28 de mayo de 2010

Pentagramas para la web

Notación musical en el Canvas

¿Que tal si se pudiera escribir música en una notación como la que se muestra a continuación y que fuera representada visualmente cómo muestra la figura? Mohit Muthanna ha implementado la noción de pentagrama utilizando el elemento canvas de HTML5 (algo que Internet Explorer no implementará). La versión actual es reciente aún, y todavía necesita un DSL para escribirla. Jono de Mozilla trabajó en un simple DSL basado en texto que resultó muy interesante porque permite hacerlo con solo modificar el texto en un elemento textarea. Si desea conocer los detalles continúe leyendo hasta el final. Le invito a suscribirse mediante RSS si desea estar al tanto de otras noticias del mundo de la informática, y las tecnologías web.

score {
  title: Hip Tune
  artist: Hip Person
  bar { v8 C4 D4 E4 F4 (C4 E4 G4) } 
  bar { v8 C4 D4 E4 F4 (C4 E4 G4) } repeat 3
}
 

La API luce actualmente de la siguiente forma:

function VexNotationDemo1(b) {
    b = new Vex.Music.Artist(b, {
        scale:0.9, width:900
    });
    var c = b.CreateScore(),
        d = b.CreateScore();
    b.DrawScore(c);
    var e = GetBar1(b,c);
    b.DrawBar(e);
    e = GetBar2(b,c);
    b.DrawBar(e);
    e = GetBar3(b,c);
    b.DrawBar(e);
    e = GetBar4(b,c);
    b.DrawBar(e);
    c = GetBar5(b,c);
    b.DrawBar(c);
    b.DrawScore(d);
    c = b.CreateContinuingBarFrom(c,d);
    b.DrawBar(c);
    d = GetBar7(b,d);
    b.DrawBar(d)
}
    
function GetBar4_2(b,c) { 
    c = b.CreateBar(c);
    var d = c.AddLine();
    d.AddNote(b.CreateNote({keys:["f##/4"],duration:"h"}));
    var e = [];
    e.push(b.CreateNote({keys:["a##/4"],duration:"16"}));
    e.push(b.CreateNote({keys:["f##/5"],duration:"16"}));
    e.push(b.CreateNote({keys:["f##/5"],duration:"16"}));
    e.push(b.CreateNote({keys:["f##/5"],duration:"16"}));
    e.push(b.CreateNote({keys:["f#/4","a/4","f/5"],duration:"16"}));
    e.push(b.CreateNote({keys:["f#/4","a/4","f/5"],duration:"16"}));
    d.AddNotes(e);
    e = b.CreateBeam(e);
    d.AddBeam(e);
    e = b.CreateNote({keys:["db/4"],duration:"32"});
    var f = b.CreateNote({keys:["f#/4"],duration:"32"}),
        g = b.CreateNote({keys:["db/4"],duration:"32"}),
        h = b.CreateNote({keys:["f#/4"],duration:"32"});
    d.AddNote(e);
    d.AddNote(f);
    d.AddNote(g);
    d.AddNote(h);
    b = b.CreateBeam([e,f,g,h]);
    d.AddBeam(b);
    return c;
}
 

SVG podría ser también una buena opción para hacer esto. En cualquier caso sería genial tener un control que generara el audio a partir de la partitura, no creen?

miércoles, 26 de mayo de 2010

Control de usuarios personalizado con Apache y mod_authnz_external

Apache Software Foundation

A medida que pasa el tiempo me convenzo más de que es correcto separar la lógica de autentificación de los usuarios y el código de las aplicaciones web. En este artículo explicaré una alternativa para delegar estas tareas al servidor Apache, pero sin tener que escribir un nuevo módulo. Por fortuna existen muchas extensiones de propósito específico para hacer esto. Un ejemplo notable es mod-auth-pam, el cual expande las posibilidades pues se aprovecha toda la flexibilidad que ofrece la arquitectura Pluggable Authentication Modules. Pero por ejemplo, ¿qué hacer si ya tenemos una aplicación que brinda acceso a los usuarios de una forma muy particular y queremos reutilizar en nuevas aplicaciones ese método y los mismos datos? Continúe leyendo este artículo y encontrará la vía de solución. Notará que la configuración es relativamente fácil de hacer en sistemas GNU/Linux como Ubuntu . No olvide suscribirse a este blog si desea conocer más trucos del mundo de la informática.

¿Qué es mod_authnz_external?

Existen muchas extensiones de propósito específico para validar la identidad de los usuarios. La lista incluye a authn_alias, authn_anon, authn_dbd, authn_dbm, authn_default, authn_file, authnz_ldap, authz_dbm, authz_default, authz_groupfile, authz_host, authz_owner, authz_user, mod-auth-kerb, mod-auth-mysql, mod-auth-pgsql, mod-auth-plain, authcassimple-perl, authenntlm-perl, mod-auth-cas, mod-auth-openid, mod-auth-radius, mod-auth-sys-group, mod-authn-sasl, mod-authnz-external, mod-authz-unixgroup, webauth. El módulo mod_authnz_external es un módulo de Apache que permite autenticar los usuarios contra servicios externos. Sus ventajas consisten en que se puede utilizar para construir rápidamente infraestructuras de este tipo que sean seguros y fiables. Sin embargo hay que ser muy cuidadosos pues, si se utiliza mal, puede ser una fuente de vulnerabilidades. Esto se debe a que se delega todo el trabajo a un proceso externo que ejecuta un script configurado para este próposito. Además, debido a su diseño, no siempre es posible utilizarlo junto con autentificación del tipo Digest. Por tal razón frecuentemente se necesitaría transmitir las claves como texto plano. Una solución a este problema sería utilizar SSL. Pero, bueno, vayamos al grano ...

Seguridad web basada en los usuarios del sistema

Una de las aplicaciones más notables de este módulo es poder reutilizar la base de datos de usuarios locales del sistema para proteger una aplicación web, sin tener que exponer el fichero shadow. Para eso utilizaremos pwauth. Solo con unas pocas líneas ya es posible saber lo que hace

$ sudo apt-get install pwauth
$ sudo pwauth
myuser
mypassword
$ echo $?
0
$ sudo pwauth
myuser
wrong_password
$ echo $?
1

Como se puede observar el programa lee el usuario y la contraseña desde la entrada estándar y facilita el resultado a través del código de retorno del proceso (el valor 0 indica que los datos son correctos). Para ilustrar el resto del proceso, supongamos que tenemos una instancia de Trac corriendo con FastCGI. Para utilizaríamos unas líneas como las que aparecen a continuación:

ScriptAlias /trac /var/lib/trac/share/cgi-bin/trac.fcgi
<Location "/trac">
  Order allow,deny
  Allow from all
</Location>
ApacheConf

Para lograr nuestro objetivo solo debemos instalar el módulo y habilitarlo.

$ sudo apt-get install libapache2-mod-authnz-external
$ sudo ln -s /etc/apache2/mods-available/authnz_external.load /etc/apache2/mods-enabled/authnz_external.load

Luego procedemos a modificar el archivo de la siguiente forma.

# Registrar el método 'unix' para ejecutar `pwauth` e 
# interactuar a través de la entrada y salida estándar
DefineExternalAuth unix pipe /usr/sbin/pwauth   

ScriptAlias /trac /var/lib/trac/share/cgi-bin/trac.fcgi
<Location "/trac">
  Order allow,deny
  Allow from all

  # Autentificación básica
  AuthType Basic

  # Utilice un valor de su preferencia
  AuthName "swl"

  # El módulo `authnz_external` se hará cargo
  # de verificar la identidad de los usuarios
  AuthBasicProvider "external"

  # Para esta aplicación se utilizará el script identificado como 
  # 'unix' (ver la directiva `DefineExternalAuth` más arriba ;o)
  AuthExternal "unix"

  # Proteger el sitio (denegar acceso anónimo)
  require valid-user
</Location>
ApacheConf

¡Es así de simple! Espero que les sea útil en su quehacer cotidiano. Le sugiero suscribirse a este blog pues en próximos artículos podrán conocer cómo escribir su propio utilitario para validar los nombres de usuarios y las contraseñas.

jueves, 29 de abril de 2010

¡ Bienvenido Ubuntu Lucid Lynx !

Hoy se espera el lanzamiento oficial de Ubuntu 10.04 (Lucid Lynx) . Vendrá con 3 años de soporte para estaciones de trabajo y cinco para servidores. A continuación les muestro el calendario de las distribuciones y la armonización con Debian squeeze que se ha considerado.

Calendario de las distribuciones

Armonización con Debian squeeze

Ubuntu Debian RHEL SLES
Kernel 2.6.32 + drm-33 2.6.32 + drm-33 2.6.32 2.6.32
GCC 4.4 4.4
Python 2.6 2.6
OpenOffice.org 3.2 3.2
Perl 5.10.1 5.10.1
Boost 1.40 1.40
X Server 1.7 1.7
Mesa 7.7 7.7
Mono (thanks Jo Shields) 2.4-snapshot 2.4-snapshot

miércoles, 21 de abril de 2010

Kohsuke Kawagushi se despide de Sun (Oracle). ¿También Hudson?

Kohsuke Kawagushi a la izquierda

En entradas anteriores ya les había comentado acerca de la fusión entre Oracle y Sun, y comentaba acerca de las implicaciones de la alianza. Brevemente quería hacerles saber que a principio de este mes de abril Kohsuke Kawagushi 1 ha decidido pasar página a sus años como empleado de Sun. Según el anuncio oficial en el blog de Hudson, en lo sucesivo sus esfuerzos estarán dirigidos a organizar una compañía que promueva el desarrollo del producto que le ha hecho mundialmente reconocido: el servidor de integración continua Hudson.

La calidad de este software está avalada por su vasta comunidad de usuarios y los galardones que le han sido otorgados. Entre ellos podemos mencionar el 2008 Duke's Choice Award. Hudson tiene un lado comercial conocido como Sun Continuous Integration Server. Realmente no se menciona nada sobre la posibilidad de ser asumido por Oracle. Solo se conoce que

We are revisiting the support offering as we transition into Oracle. Please check back later for updates.

Sin embargo espero que, en el global, todo esto beneficie al producto y mantenga viva su comunidad de usuarios. Para más detalles le invito a seguir el blog de Hudson. Próximamente (cuando aprenda dos o tres cosas ;o) espero poder comentarles un poco más acerca de este excelente producto. Simelo pide, no faltará un consejo útil para que Usted comience a experimentar los beneficios de la integración continua. No pierda las oportunidad de estar actualizado.

jueves, 15 de abril de 2010

Soporte para AMF (RPC) en Trac

Parche de la API multi-protocols para Trac

Otro día histórico. Después que Odd Simon Simonsen me invitara a participar en el desarrollo del muy útil y exitoso plugin TracXmlRpc y añadir soporte para Hessian, le ha tocado el turno a Action Message Format. Este es un protocolo binario diseñado por Macromedia, actualmente absorbido por Adobe Systems. Su objetivo es proporcionar un medio ligero y eficiente para codificar, decodificar y transportar datos entre Flash Player y el Flash Remoting gateway. En otras palabras se puede utilizar Flash Remoting MX con ActionScript para hacer aplicaciones interactivas, en este caso utilizando datos de proyectos administrados por Trac. El nuevo componente que ha sido construido permite tener más elementos de juicio para evaluar la API que se desarrolla actualmente, y que permitirá ejecutar los métodos disponibles a través de múltiples protocolos. Hace solo unos minutos efectué la primera llamada exitosa utilizando AMF desde Python . Los detalles ...

... no quiero adelantar mucho todavía porque la implementación no está estable y muy probablemente se le hagan cambios incompatibles al prototipo de la API que existe hoy. Pero bueno, todo comenzó con un parche ofrecido por thijs para incorporarlo en el plugin plugin TracXmlRpc. En ese momento solo existían implementaciones para dos protocolos muy populares: XML-RPC, JSON-RPC. Hay soporte para el primero en la librería estándar (i.e. xmlrpclib.ServerProxy), mientras que el segundo es accesible si se utilizan otras librerías (e.g. wsgi-jsonrpc). En el caso específico de AMF, el autor del parche ofreció un ejemplo basado en PyAMF que luego modifiqué un poco hasta obtener lo siguiente :

#!/usr/bin/env python

"""
AMF client for the Trac RPC plugin.
"""

import base64
from optparse import OptionParser
from socket import error
import sys

from pyamf.remoting import RemotingError
from pyamf.remoting.client import RemotingService

p = OptionParser()

p.add_option("-U", dest="username",
                  help="Trac USER", metavar="USER")
p.add_option("-P", dest="password", metavar="PASSWORD",
                  help="use PASSWORD to authenticate USER")
p.add_option("-e", dest="env", metavar="URL",
                  help="base URL of target environment")

(opts, args) = p.parse_args()

username = opts.username
password = opts.password
try :
  url = opts.env + '/rpc'
except :
  sys.exit("Missing Trac environment. Use -e option.")

service_name = 'system'

gw = RemotingService(url)
if (username, password) != (None, None):
  auth = base64.encodestring('%s:%s' % (username, password))[:-1]
  gw.addHTTPHeader("Authorization", "Basic %s" % auth)

service = gw.getService(service_name)

try:
    print service.getAPIVersion()
except RemotingError, e:
    print e
except error, e:
    print e[1]

Como se puede ver, si la base del servidor se encuentra, por ejemplo, en http://localhost/trac entonces el protocolo está disponible en http://localhost/trac/rpc. Realmente esta URL debe servir para todos los protocolos disponibles que ya mencioné anteriormente y los que vendrán. El tipo de protocolo se determina considerando el encabezado Content-Type de la petición HTTP, que en este caso es application/x-amf. Esta es la idea fundamental que utilizó Odd para diseñar la API.

El script anterior muestra el número de la versión del protocolo. Para probar, ejecutamos el módulo de la siguiente forma

$ python ./amf-client.py -U username -P mypassword -e http://localhost/trac
[1, 1, 0]

¡Y ya está! . A continuación les muestro otro ejemplo más simplificado utilizando solamente el intérprete de Python.

>>> import base64
>>> from pyamf.remoting import RemotingError
>>> from pyamf.remoting.client import RemotingService
>>> username, password = 'olemis', 'mypassword'
>>> url = 'http://localhost/trac/rpc'
>>> gw = RemotingService(url)
>>> auth = base64.encodestring('%s:%s' % (username, password))[:-1]
>>> gw.addHTTPHeader("Authorization", "Basic %s" % auth)
>>> service = gw.getService('system')
>>> print service.getAPIVersion()
[1, 1, 0]

¡Eso es todo! Todas estas implementaciones serán ofrecidas próximamente por el plugin TracRpcProtocols. Si quiere estar al tanto del desarrollo de esta nueva funcionalidad le invito a seguir los próximos artículos. El soporte de AMF para Trac es solo el segundo capítulo de la primera temporada (gracias osimons y thijs ;o) .

martes, 6 de abril de 2010

Demanda sobre patente impide distribuir Microsoft Word

Microsoft Office

El gigante Microsoft acaba de ver rechazada su petición a la corte de apelaciones de los Estados Unidos. Por segunda vez, la firma sigue siendo acusada de haber violado las patentes relacionadas con el procesamiento de ficheros XML en el producto Microsoft Office Word . El 22 de diciembre pasado el tribunal había dictaminado la indemnización por un monto de $ 290 millones USD que engrosarían las arcas de la sociedad canadiense i4i. Además se ordenó la prohibición de la venta de su producto Microsoft Word en territorio estadounidense. Recientemente la sociedad canadiense i4i explica en un comunicado que la instancia judicial ha rechazado la demanda del gigante de Redmond con el objetivo de que un panel de 11 jueces reconsidere el caso. 

Loudon Owen, presidente de i4i declaró que "El proceso fue largo y desgastante, pero esta decisión refuerza fuertemente el mensaje de que las pequeñas empresas y los titulares de las invenciones amparados por la propiedad intelectual pueden ser protegidos, y lo serán" . Como resultado Microsoft está obligado a retirar inmediatamente la función XML de Microsoft Word 2003 y 2007 . Sin embargo los recursos no están agotados. Todavía es posible continuar el proceso de apelación o solicitar al veredicto de la Corte Suprema para seguir revisando el caso.

lunes, 29 de marzo de 2010

Comparación de sistemas de control de versiones (a.k.a.VCS o SCM)

En esta breve entrada quería invitarles a que visiten el sitio http://versioncontrolblog.com , sobre todo si están tratando de decidir cuál sistema de control de versiones utilizarán. Allí es posible ver una tabla que compara las funcionalidades de los sistemas que se seleccionen teniendo en cuenta varios criterios como.

  • Commits atómicos
  • Soporte para seguir copias de ficheros y carpetas
  • Duplicación remota del repositorio
  • Control de acceso
  • Posibilidad de trabajar en un único sub-directorio
  • Interfaz web
  • Disponibilidad de interfaces de usuario gráficas
  • ... mucho más ...

Espero les sea útil el consejo ;o)

Tutorial: Cómo crear enlaces directos en Windows con Python

Enlaces directos en Windows

En esta ocasión trataré de resumir varios artículos escritos por Mike Driscoll y Tim Golden. Todos estos tutoriales tratan de explicar diferentes vías para crear desde Python enlaces directos (a.k.a shortcuts) para acceder a aplicaciones de Windows. Mi intención es, primeramente, acordarme. En segundo lugar quisiera facilitar documentar en un solo lugar los enfoques disponibles, y tratar de ilustrar las diferencias más ventajas o desventajas. Finalmente el texto estará en español ;o). Antes que todo, si todavía no se ha dado cuenta le digo que solo hablaré de Microsoft Windows. El artículo puede servir también como un pequeño mini-tutorial acerca del uso de los módulos winshell, win32com, pythoncom y habrá hasta un poquito de corrutinas ;o). Si desea estar al tanto de próximos artículos igualmente útiles, le invito a suscribirse por medio de RSS.

Preparación

Python Programming on Win32

Lo primero que hay que hacer es instalar las extensiones de Python para Win32 que se encuentran en el paquete de Mark Hammond llamado PyWin32. Igualmente útil puede resultar tener listo el módulo winshell de Tim Golden. Este último permite ubicar fácilmente las carpetas especiales (semi-mágicas) del sistema.

Creando un enlace a una URL en el Escritorio

Quizás la tarea más sencilla es crear un enlace (a.k.a link) en el Escritorio para acceder fácilmente a un sitio conocido. En caso que sea suficiente utilizar el navegador de Internet predeterminado, solo tendríamos que hacer esto:

import os.path, winshell

desktop = winshell.desktop()
path = os.path.join(desktop, "myNeatWebsite.url")
target = "http://www.google.com/"
 
shortcut = file(path, 'w')
shortcut.write('[InternetShortcut]\n')
shortcut.write('URL=%s' % target)
shortcut.close()

Brevemente podemos apreciar cómo se utiliza el módulo winshell para obtener el camino al Escritorio del usuario y se crea un fichero con extensión .url dentro de esta carpeta. El contenido de este fichero es el requerido por el sistema operativo y, finalmente debe lucir más o menos así

[InternetShortcut]
URL=http://www.google.com/

¡Eso es todo! :o)

La solución a más bajo nível con pythoncom

Quizás la versión más complicada, y por tanto más explícita, es la que se obtiene al utilizar la API Win32 desde Python. Para ello se emplea el módulo pythoncom, que permite cargar cualquier objeto COM e interactuar casi directamente con las interfaces y otros elementos. Veamos el ejemplo.

import os, sys
import pythoncom
from win32com.shell import shell, shellcon
 
shortcut = pythoncom.CoCreateInstance (
  shell.CLSID_ShellLink,
  None,
  pythoncom.CLSCTX_INPROC_SERVER,
  shell.IID_IShellLink
)
program_location = r'C:\Program Files\SomeCoolProgram\prog.exe'
shortcut.SetPath (program_location)
shortcut.SetDescription ("My Program Does Something Really Cool!")
shortcut.SetIconLocation (program_location, 0)
 
desktop_path = shell.SHGetFolderPath (0, shellcon.CSIDL_DESKTOP, 0, 0)
persist_file = shortcut.QueryInterface (pythoncom.IID_IPersistFile)
persist_file.Save (os.path.join (desktop_path, "My Shortcut.lnk"), 0)

Como se puede apreciar esto no es muy pitónico que digamos, sobre todo debido a que la API Win32 está diseñada para ser utilizada desde lenguajes como C. Más adelante se verán otras formas más sencillas de hacerlo. Sin embargo, creo que es un ejemplo muy interesante porque revela que los conocedores de la API Win32 siempre podrán utilizarla desde Python de una forma muy similar a la que ya están acostumbrados. Veamos qué está pasando.

Después de importar los módulos necesarios, se carga una instancia de la clase ShellLink y se indica que se utilizarán los métodos de la interfaz IShellLink que ella implementa. Por suerte los GUID de ambos ya están definidos (i.e. win32com.shell.shell.CLSID_ShellLink, win32com.shell.shell.IID_IShellLink). La función pythoncom.CoCreateInstance no es más que un simple wrapper alrededor de la archi-conocida función con un nombre similar presente en la API Win32. A continuación se especifican los datos necesarios para crear el enlace (i.e. programa a ejecutar, descripción y el ícono). En este momento ya todo está casi listo.

Lo único que falta por hacer es crear el fichero con la información que hemos especificado anteriormente. El último bloque de instrucciones se encarga de esto. La función win32com.shell.shell.SHGetFolderPath es similar a la presente en la API Win32. En este caso se utiliza la constante win32com.shell.shellcon.CSIDL_DESKTOP para obtener el camino a la carpeta del escritorio del usuario. Allí se creará el fichero My Shortcut.lnk, que es el que deseamos. Para hacerlo, hay que utilizar el objeto COM anterior pero necesitamos el método Save definido en la interfaz IPersistFile (su GUID es pythoncom.IID_IPersistFile). El método QueryInterface hace todo el trabajo de preparación.

A continuación les presento un código que utiliza la misma filosofía para actualizar el enlace creado en la sección anterior y hacer que apunte a otra URL.

import os, sys
import pythoncom
from win32com.shell import shell, shellcon

shortcut = pythoncom.CoCreateInstance (
  shell.CLSID_InternetShortcut,
  None,
  pythoncom.CLSCTX_INPROC_SERVER,
  shell.IID_IUniformResourceLocator
)

desktop_path = shell.SHGetFolderPath (0, shellcon.CSIDL_DESKTOP, 0, 0)
shortcut_path = os.path.join (desktop_path, "myNeatWebsite.url")
persist_file = shortcut.QueryInterface (pythoncom.IID_IPersistFile)
persist_file.Load (shortcut_path)

shortcut.SetURL ("http://python.org")
persist_file.Save (os.path.join (shortcut_path, "myNeatWebsite.url"), 0)

Creando un enlace al Media Player Classic con win32com

Python

Este ejemplo será un tin más complejo. Utilizaremos el módulo win32com para crear un enlace. Este módulo brinda acceso desde Python a ciertos componentes implementados sobre la tecnología COM, así que se trata de una operación digamos que a bajo nivel. Sin embargo, La ventaja en este caso consiste en que se facilitan varios pasos que sí son necesarios si se utiliza la API Win32 directamente con otros lenguajes (e.g. C, Object Pascal), o cómo se mostró en el ejemplo anterior. Veamos el código.

import os, winshell
from win32com.client import Dispatch
 
desktop = winshell.desktop()
path = os.path.join(desktop, "Media Player Classic.lnk")
target = r"P:\Media\Media Player Classic\mplayerc.exe"
wDir = r"P:\Media\Media Player Classic"
icon = r"P:\Media\Media Player Classic\mplayerc.exe"
 
shell = Dispatch('WScript.Shell')
shortcut = shell.CreateShortCut(path)
shortcut.Targetpath = target
shortcut.WorkingDirectory = wDir
shortcut.IconLocation = icon
shortcut.save()

Como se puede apreciar, en este caso se utiliza el shell de Windows mediante la interfaz IDispatch (más bien su equivalente en Python win32com.client.Dispatch ;o). Se utiliza el identificador WScript.Shell para cargar el objeto COM que deseamos y luego se utilizan sus métodos para asociarle el camino donde se guardará el enlace, el del ejecutable, el directorio de trabajo inicial, y el ícono que se mostrará. Este último puede ser el camino a un fichero .ico o a un ejecutable o dll'. La última llamada al método Save es muy importante, pues es la que hace que el enlace se haga persistente (i.e. se escriba en el disco)

A continuación les muestro una función que puede ser útil para manejar ambos casos.

from win32com.client import Dispatch
import os.path
 
def createShortcut(path, target='', wDir=None, icon=''):
    if wDir is None :
        wDir = os.path.dirname(os.path.abspath(target))
    ext = path[-3:]
    if ext == 'url':
        shortcut = file(path, 'w')
        shortcut.write('[InternetShortcut]\n')
        shortcut.write('URL=%s' % target)
        shortcut.close()
    else:
        shell = Dispatch('WScript.Shell')
        shortcut = shell.CreateShortCut(path)
        shortcut.Targetpath = target
        shortcut.WorkingDirectory = wDir
        if icon <> '':
            shortcut.IconLocation = icon
        shortcut.save()

Creando el enlace en Archivos de programas utilizando winshell

Ya hemos visto varias variantes pero sin dudas esta resultará la más económica en cuanto a líneas de código.

import os, winshell
 
program_files = winshell.programs()
winshell.CreateShortcut (
               Path=os.path.join (program_files, 'My Shortcut.lnk'),
               Target=r'C:\Program Files\SomeCoolProgram\prog.exe')

En este caso se guardará el enlace My Shortcut.lnk en la carpeta Archivos de programas del menú de inicio del usuario actual. El camino lo devuelve winshell.programs(). La función winshell.CreateShortcut hace exactamente lo que necesitamos. Además de los argumentos mostrados en el ejemplo, también es posible utilizar los siguientes:

  • Path : el camino dónde se creará el enlace
  • Target : el fichero que se ejecutará al activar el enlace
  • Arguments : Los argumentos (i.e. línea de comandos) que recibirá la aplicación
  • StartIn? : directorio de trabajo inicial
  • Icon : ícono que se mostrará en el Explorador de Windows
  • Description : Descripción del enlace

Resolviendo los enlaces

En este caso trataremos de implementar para Windows una funcionalidad que se obtiene casi sin esfuerzo al utilizar los enlaces simbólicos de sistemas Unix. A diferencia de este último, en Windows los enlaces directos son ficheros comunes y corrientes para el sistema de archivos, pero como ya vimos para el shell representan objetos de tipo IShellLink. Esta diferencia nos obliga a poner el parche porque ya salió el hueco ;o). A continuación leeremos una lista de ficheros y, si encontramos un enlace directo, lo seguiremos para retornar el fichero de destino.

import os, sys
import glob
import pythoncom
from win32com.shell import shell, shellcon

def shortcut_target (filename):
  link = pythoncom.CoCreateInstance (
    shell.CLSID_ShellLink,    
    None,
    pythoncom.CLSCTX_INPROC_SERVER,    
    shell.IID_IShellLink
  )
  link.QueryInterface (pythoncom.IID_IPersistFile).Load (filename)
  #
  # GetPath devuelve el camino y una estructura de tipo `WIN32_FIND_DATA`
  # que se ignorará. El parámetro indica si se desea obtener el 
  # nombre corto, el UNC o el camino (a.k.a. "raw path").
  # Por alguna extraña razón la documentación indica que las 
  # constantes correspondientes se pueden combinar :-$ .
  #
  name, _ = link.GetPath (shell.SLGP_UNCPRIORITY)
  return name

def shell_glob (pattern):
  for filename in glob.glob (pattern):
    if filename.endswith (".lnk"):
      yield (filename, shortcut_target (filename))
    else:
      yield (filename, filename)

desktop = shell.SHGetSpecialFolderPath (None, shellcon.CSIDL_DESKTOP)
for filename in shell_glob (os.path.join (desktop, "*")):
  print filename

¿Se podría mejorar este ejemplo utilizando winshell?

Conclusiones

Hay múltiples variantes para utilizar Python con el fin de crear enlaces directos en Windows. La más sencilla parece ser la que utiliza la función winshell.CreateShortcut. Sin embargo, hay tela por donde cortar y níveles de complejidad para todos los gustos.

Espero que le haya sido útil este corto tutorial. Si es así, le invito a estar al tanto de los próximos artículos. Trataré de abordar temas tan útiles y recurrentes como este. Me sentiría satisfecho si logro sacarle de un apuro o darle una idea para mejorar sus scripts de Python.