joe di castrohttp://joedicastro.com2012-06-07T22:40:00+02:00Productividad & Linux: Aplicaciones2012-06-07T22:40:00+02:00joe di castrohttp://joedicastro.com/productividad-linux-aplicaciones.html<p>En la anterior serie de artículos sobre <a href="http://joedicastro.com/productividad-en-el-escritorio-linux-introduccion.html">Productividad en el escritorio Linux</a>, hacia referencia a los lanzadores de aplicaciones y a los gestores de ventanas de mosaico como elementos diferenciadores a la hora de ser más productivos. Aunque la productividad en el escritorio es fundamental para trabajar de forma más eficiente, son las aplicaciones que empleamos, las que en último lugar, determinaran la realidad de nuestro día a día frente a la pantalla.</p> <p>Gran parte de las aplicaciones para Linux (las más conocidas y extendidas) son realmente una adaptación de los conceptos de los sistemas operativos más difundidos: Windows &amp; Mac OS. Esto es, aplicaciones muy pesadas, repletas de opciones y funciones de las que se aprovechan en realidad muy pocas. Además están por lo general fuertemente orientadas al empleo del ratón y muchas veces con un interfaz gráfico muy agradable visualmente pero con un concepto de usabilidad, en mi opinión, no siempre bien enfocado.</p> <h2 id="la_usabilidad_mal_entendida_como_enemiga_de_la_productividad">La usabilidad mal entendida, como enemiga de la productividad</h2> <p>La <a href="https://es.wikipedia.org/wiki/Usabilidad">usabilidad</a>, ese concepto tan de moda últimamente, está desde mi punto de vista, frecuentemente muy mal enfocado. Dejando a un lado lo que teóricamente se pretende, en el 80% de las ocasiones esto se traduce en la práctica en dos palabras en ingles: <em>For dummies</em> ("pá tontos", en castizo). Es decir, que cualquiera pueda manejar la aplicación sin grandes dificultades y en el menor tiempo posible. Reducir las barreras de entrada está muy bien, pero desgraciadamente muchas veces esto es una estrategia a corto plazo que no debería emplearse <strong>NUNCA de este modo</strong> en aplicaciones profesionales. Entiendo, y además apoyo, está estrategia en aplicaciones web y aplicaciones de consumo, pero lo veo nocivo a la hora de aplicaciones destinadas a producir datos y contenidos de forma masiva e intensiva. Y a mi modo de ver es un problema serio en un gran número de escenarios.</p> <h3 id="un_caso_real_de_usabilidad_enga+osa">Un caso real de usabilidad engañosa</h3> <p>No proporcionare datos reales por respeto a las partes implicadas, pero quiero aportar aquí un ejemplo real que he podido vivir de cerca durante varios años, de lo que la pretendida usabilidad, ni lo es, ni ayuda a sacar más partido de las herramientas:</p> <p><strong>Escenario fallido</strong> : un negocio que se dedica el 90% del tiempo a editar grandes cantidades de texto. Se informatizan a mediados de los 80 y adoptan como procesador de textos WordPerfect para consola. Después de varios años, están en la versión de WordPerfect 5.1 para MSDOS con un 90% de los empleados (unos diez), con un dominio asombroso de la aplicación, basando todo su trabajo en atajos de teclado y una pantalla donde únicamente se visualiza el texto sobre el que están trabajando. Productividad: realmente elevada. Principios del siglo XXI, un consultor informático embaucador (<em>aka</em> comercial) los convence de que el futuro es <a href="https://es.wikipedia.org/wiki/Wysiwyg">WYSIWYG</a> y que eso del terminal de texto es cosa del pasado. Tras una primera migración a WordPerfect para Windows, la productividad baja de forma significativa. La solución propuesta: Word, la productividad cae de forma dramática. La excusa del "asesor": necesidad de adaptación al nuevo concepto. Dos años después, la productividad sigue sin incrementarse, el trabajo se acumula y se contratan a dos personas más para refuerzo. El personal ya se ha adaptado y están tan hipnotizados por el ratón, los iconos y los "regalitos" que su software les proporciona en cada nueva versión, que se oponen radicalmente a volver al "pasado". En realidad, nadie desde dentro se cuestionaba que el problema real era que habían dejado de centrarse en el contenido, para centrarse en el continente. Lo más irónico: lo que realmente hacían era crear e imprimir documentos, impresos en impresoras matriciales que seguían imprimiendo con la misma calidad y la misma fuente Courier 11 sobre el mismo tipo de documentos oficiales.</p> <p><strong>Escenario exitoso</strong> : Paralelamente, en un área de negocio distinta, pero estrechamente relacionada con la anterior, una empresa que ha trabajado siempre en entorno de terminal contra un <a href="https://es.wikipedia.org/wiki/As/400">AS/400,</a> presentaba una mentalidad y un resultado totalmente opuestos. Nunca se han dejado seducir por los cantos de sirena del marketing y se han opuesto radicalmente a cualquier cambio de sistema, simplemente han evolucionado el que ya tenían y conservando su plantilla prácticamente intacta durante el mismo periodo. El resultado: la productividad se ha mantenido constante a lo largo de los años y han podido asumir importantes picos de carga de trabajo sin demasiadas dificultades. Y un efecto secundario de esta toma de decisiones: el haberse mantenido dentro de un sistema maduro y estable les ha librado de un mantenimiento casi nulo en el área de software, limitándose el mismo al área de hardware (lo habitual). Como podéis imaginar, en un sistema basado en plataforma Windows y actualizaciones sucesivas de SO y aplicaciones, el mantenimiento del área de software les ha supuesto una importante carga económica al otro negocio.</p> <p>Que cada uno extraiga sus propias conclusiones. La mía: que la carrera hacia adelante de fabricantes de sistemas operativos y aplicaciones (cuyo único recurso para seguir vendiendo ha sido con frecuencia un simple lavado de cara) ha supuesto, en muchos aspectos, un paso atrás en varias áreas de la informática.</p> <h2 id="la_usabilidad_bien_entendida">La usabilidad bien entendida</h2> <p>No quiero decir con lo anterior, que las aplicaciones gráficas sean el problema ni mucho menos. De hecho, hay aplicaciones gráficas que son una maravilla de la usabilidad. Por ejemplo, Autocad, que te permite un control casi absoluto como usuario avanzado (con los comandos y <a href="https://es.wikipedia.org/wiki/Autolisp">AutoLISP</a>) y que al mismo tiempo te permite realizar gran parte del trabajo de manera muy intuitiva (siempre teniendo en cuenta el contexto del usuario al que va dirigido) empleando la interfaz gráfica. Lo que permite que sea muy usable sin detrimento de la productividad. Lo que quiero decir, es que es necesario añadir el sentido común como un criterio más, si no el primero, a la hora de implementar la usabilidad.</p> <p>Si consultamos la Wikipedia, encontramos dos definiciones de la ISO para el concepto de <a href="https://es.wikipedia.org/wiki/Usabilidad">usabilidad</a> :</p> <blockquote> <p>La usabilidad se refiere a la capacidad de un software de ser comprendido, aprendido, usado y ser atractivo para el usuario, en condiciones específicas de uso</p> <p>Usabilidad es la eficacia, eficiencia y satisfacción con la que un producto permite alcanzar objetivos específicos a usuarios específicos en un contexto de uso específico</p> </blockquote> <p>Como decía anteriormente, la realidad dista mucho de la teoría. En la práctica se ha adoptado con bastante fidelidad la primera definición, eso sí, ignorando con bastante frecuencia la parte de "condiciones específicas" y la segunda definición parece haber sido ampliamente ignorada.</p> <p>Si bien es cierto que la mayoría de las aplicaciones actuales pueden ser muy potentes, lo que yo veo mirando al pasado, es que la gran mayoría de los empleados que utilizaban una aplicación a diario eran usuarios avanzados y cuando no expertos. Hoy en día, y ciñéndonos al ámbito profesional, a algunos les cambias el icono de la misma, y no saben iniciarla. Y no es culpa de ellos, las aplicaciones se diseñan para que las puedan usar hasta los más reacios a la tecnología (siempre en su contexto), pero una vez que necesitas realizar algo complejo, existe un salto conceptual que la mayoría de los usuarios, ni se atreven a dar, ni quieren darlo.</p> <p>Es realmente complejo, muy complejo, conseguir una interfaz de usuario que sea realmente amigable e intuitiva y que al mismo tiempo consiga satisfacer las necesidades reales tanto del usuario profesional, como del usuario ocasional. El problema de base está en intentar crear herramientas todoterreno. Ni maldita falta que le hace un Word actual a un usuario domestico y que demonios hace un escritor profesional empleándolo en vez de algo como Latex + editor de texto. Ni un usuario domestico necesita de herramientas para correspondencia, programar macros, etc, ni un escritor necesita preocuparse del formato del texto. Y sin embargo es empleado hasta por editores que deberían estar empleando herramientas de autoedición.</p> <p>Por ejemplo, una aplicación que es un claro ejemplo de buena usabilidad y que está teniendo una buena acogida últimamente, es <a href="http://www.sublimetext.com/">Sublime Text</a>. Es bonita, es potente, es amigable y es francamente muy, muy usable. Realmente lo que han hecho ha sido recoger el testigo que dejo <a href="http://macromates.com/">TextMate</a>, que fueron los primeros en darse cuenta de algo muy sencillo. Esto es, los editores de texto y los IDEs para programadores son una de las aplicaciones en las que nunca llueve a gusto de todos, surgen continuamente nuevas alternativas, pero no se acaba por imponer ninguna. Por que en realidad, si nos fijamos en los programadores más experimentados, dos de los editores más utilizados y defendidos a muerte, son <a href="https://es.wikipedia.org/wiki/Vim">Vim</a> y <a href="https://es.wikipedia.org/wiki/Emacs">Emacs</a>, ambos basados en conceptos de hace casi cuatro décadas. Es que la usabilidad ya estaba inventada hace mucho tiempo, y no tiene nada que ver ni con gráficos bonitos, ni con tratar a los usuarios como imbéciles. La usabilidad, señores, está en la adaptación del interfaz del ordenador de la manera más optima posible al interfaz que la naturaleza nos dio, nuestras manos. Y desde luego, jamás un ratón, con los botones que quieras ponerle, sera más eficaz que un teclado (usado con los diez deditos), al igual que no lo es más que un bolígrafo o una tableta gráfica con lápiz. Que para eso tenemos los dedos y no muñones. Si vamos a comprar un libro en Internet, el ratón es nuestro gran aliado para hacerlo de forma intuitiva sin conocimientos previos, pero si me vas a obligar a introducir entradas de almacén 40 horas a la semana con la misma aplicación, por favor, no me condenes a usar el ratón cada dos pasos. Sentido común!</p> <h2 id="las_aplicaciones_amigas_de_la_productividad">Las aplicaciones amigas de la productividad</h2> <p>Este entrada pretende ser la introducción y el indice de una nueva serie de artículos dedicada a esas aplicaciones para Linux que para mi marcan la diferencia entre <em>hacer el trabajo</em> y <em>marear la perdiz</em>. Estas aplicaciones comparten en general tres puntos en común:</p> <ul> <li>Son muy eficientes y ayudan a incrementar nuestra productividad.</li> <li>Realizan a una sola tarea y lo hacen bien o muy bien.</li> <li>No están muy extendidas.</li> </ul> <p>Iré actualizando la entrada a medida que vaya creando los artículos para cada aplicación.</p> <h3 id="aplicaciones">Aplicaciones</h3> <p>Estas son algunas de las aplicaciones que yo empleo a menudo y que al igual que a mí, pueden servirte para incrementar de forma notable tu productividad.</p> <ul> <li> <p><a href="http://joedicastro.com/productividad-linux-pentadactyl.html">Pentadactyl</a>, complemento para Firefox para controlar el navegador completamente desde el teclado, permite navegar entre las páginas a velocidad de vértigo. Además es muy potente y posee muchísimas opciones y posibilidades.</p> </li> <li> <p><a href="http://joedicastro.com/productividad-linux-newsbeuter.html">Newsbeuter</a>, aplicación para leer subscripciones RSS y Atom. Funciona en modo texto, con una interfaz tipo ncurses y completamente controlable desde el teclado. Es una aplicación rapidisima y con un consumo muy escaso de recursos.</p> </li> <li> <p><a href="http://joedicastro.com/productividad-linux-turses.html">Turses</a>, cliente twitter para la consola con interfaz ncurses. Controlable desde el teclado con atajos basados en Vim. Muy personalizable y con un funcionamiento muy versátil y ágil.</p> </li> <li> <p><a href="http://joedicastro.com/productividad-linux-tmux.html">tmux</a>, es un multiplexor de terminales. Permite agrupar varios terminales en una sola ventana de forma muy eficiente. La herramienta ideal para los amantes de la consola, desarrolladores y administradores de sistemas.</p> </li> <li> <p><a href="http://joedicastro.com/productividad-linux-zathura.html">zathura</a>, es un visor de documentos minimalista y controlable desde el teclado. Soporta documentos en formato PDF, DJVU, PostScript y Comic Book.</p> </li> <li> <p><a href="http://joedicastro.com/productividad-linux-ranger.html">Ranger</a>, es un administrador de archivos en modo texto y revolucionario en su interfaz y manejo. Muy potente y personalizable. </p> </li> <li> <p><a href="http://joedicastro.com/productividad-linux-ncdu.html">Ncdu</a>, una herramienta con interfaz ncurses que nos permite conocer el espacio consumido en nuestro disco duro y navegar por los distintos directorios. La mejor herramienta del estilo que existe para la consola.</p> </li> </ul>Logger, informes legibles para tus scripts Python2011-05-07T23:02:00+02:00joe di castrohttp://joedicastro.com/logger-informes-legibles-para-tus-scripts-python.html<p>Normalmente los scripts que se crean para ser ejecutados periódicamente, como los de administración de sistemas, realizan tareas que generan cambios que queremos conocer. Esta información se suele guardar en logs o se envía por correo a una dirección de email. Hay muchas formas de generar estos logs, pero normalmente es una simple salida de texto plano en consola, que no suele tener una presentación muy "amigable" o legible. Cuando tienes una cantidad generosa de estos scripts, los recibes por correo electrónico y con una frecuencia diaria o mayor, lo que menos deseas es andar buscando la información entre lineas de texto plano. Lo que yo quiero es poder identificar la información rápidamente de un vistazo, además tenía una idea rondando por la cabeza, que seria aún más cómodo si todos emplearan un formato similar. Con estas premisas cree un módulo <strong>Python</strong>, <code>logger.py</code>, que empleo en muchos de mis scripts en Python y que me permite analizar adecuadamente la información que me interesa.</p> <p>Un ejemplo de informe generado por este modulo Python sería el siguiente (el mismo que se generaría si ejecutáramos el modulo como script):</p> <div class="codehilite"><pre>SCRIPT ========================================================================= logger (ver. 0.3) http://joedicastro.com This is a test of class Logger ================================================================================ START TIME ===================================================================== Saturday 05/07/11, 21:51:27 ================================================================================ BLOCK ========================================================================== This is a sample of Logger.block() ================================================================================ LIST ___________________________________________________________________________ This is a sample of Logger.list() This a sample of logger.free() text. &quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque sed tortor eget justo vehicula consequat vel eu quam. Suspendisse non lectus eget orci varius adipiscing.&quot; END TIME ======================================================================= Saturday 05/07/11, 21:51:27 ================================================================================ </pre></div> <p>Este modulo contiene una clase, <code>Logger()</code>, que dispone de una serie de métodos que nos permiten diversas tareas, supongamos que tenemos un objeto <code>log</code> de la clase <code>Logger()</code>:</p> <ul> <li> <p><code>log.header(url, msg)</code></p> <p>Nos crea una cabecera para el script que nos aporta cierta información que nos sirve para identificar el mismo y el contexto en el que se ejecuta. </p> <div class="codehilite"><pre>SCRIPT ========================================================================= nombre del script (versión) http://miweb.com/script mensaje que nos informa de la finalidad del mismo o de información dependiente del contexto. Totalmente personalizable. ================================================================================ </pre></div> </li> <li> <p><code>log.time(title)</code></p> <p>Nos permite registrar el tiempo de un evento. Normalmente lo empleo para registrar el comienzo y el final de la ejecución del script. </p> <div class="codehilite"><pre>TITULO ========================================================================= Sábado 07/05/11, 22:18:27 ================================================================================ </pre></div> </li> <li> <p><code>log.block(title, content)</code></p> <p>Crea un bloque de texto con titulo y enmarcado por líneas compuestas por el carácter <code>=</code>. Entre esas lineas y el contenido no hay ninguna linea en blanco. Es útil para destacar cierto contenido del resto de forma muy notable, suelo utilizarlo para ubicar el script en su contexto. </p> <div class="codehilite"><pre>TITULO ========================================================================= contenido sigue el contenido ... ================================================================================ </pre></div> </li> <li> <p><code>log.list(title, content)</code></p> <p>Como su nombre bien indica, es una lista de líneas con un simple encabezado que lo distinga del resto. Suelo emplearlo para volcar la información generada por el script. Entre el encabezado y la primera línea de texto existe una línea en blanco.</p> <div class="codehilite"><pre>TITULO __________________________________________________ Primera línea del contenido Segunda línea del contenido ... </pre></div> </li> <li> <p><code>log.free(content)</code></p> <p>Texto libre, párrafos que se mostrarán tal y como son, sin formato ni cabecera alguna. Raramente lo empleo, pero es útil por ejemplo para introducir comentarios, licencias, etc.</p> </li> <li> <p><code>log.send(subject, send_from='', dest_to='', mail_server='localhost', server_user='', server_pass='')</code></p> <p>El meollo del script. Se emplea para mandar el resultado por correo electrónico. Si solo especificamos el asunto, empleará nuestro servidor de correo local para mandar el informe al buzón local del usuario que programó/ejecuto el script. Pero se pueden especificar tanto el empleo de un servidor de correo (SMTP) distinto como otro (o varios) destinatario(s) en particular. </p> </li> <li> <p><code>log.write(append=False)</code></p> <p>Escribe el resultado del log en un fichero de texto. El nombre de este fichero estará compuesto por el nombre del script sin extensión, más la extensión <em>.log</em>. Si <code>append</code> es <code>True</code> entonces añadirá el resultado al final de texto, si no, lo reescribirá en cada ejecución guardando únicamente el último informe. </p> </li> <li> <p><code>log.get()</code></p> <p>Nos devuelve el contenido del log. Es útil cuando estamos con tareas de depurado, con un <code>print</code> llamando a este método podemos volcar en la consola la información registrada en el log.</p> </li> </ul> <p>El código del script es el siguiente: </p> <div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span> <span class="c"># -*- coding: utf8 -*-</span> <span class="sd">&quot;&quot;&quot;</span> <span class="sd"> logger.py: Create a log object to log script messages in a elegant way</span> <span class="sd">&quot;&quot;&quot;</span> <span class="c">#===============================================================================</span> <span class="c"># This module create a log object to log script messages in a elegant way</span> <span class="c">#===============================================================================</span> <span class="c">#===============================================================================</span> <span class="c"># Copyright 2010 joe di castro &lt;joe@joedicastro.com&gt;</span> <span class="c">#</span> <span class="c"># This program is free software: you can redistribute it and/or modify</span> <span class="c"># it under the terms of the GNU General Public License as published by</span> <span class="c"># the Free Software Foundation, either version 3 of the License, or</span> <span class="c"># (at your option) any later version.</span> <span class="c">#</span> <span class="c"># This program is distributed in the hope that it will be useful,</span> <span class="c"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span> <span class="c"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span> <span class="c"># GNU General Public License for more details.</span> <span class="c">#</span> <span class="c"># You should have received a copy of the GNU General Public License</span> <span class="c"># along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</span> <span class="c">#===============================================================================</span> <span class="n">__author__</span> <span class="o">=</span> <span class="s">&quot;joe di castro &lt;joe@joedicastro.com&gt;&quot;</span> <span class="n">__license__</span> <span class="o">=</span> <span class="s">&quot;GNU General Public License version 3&quot;</span> <span class="n">__date__</span> <span class="o">=</span> <span class="s">&quot;10/09/2010&quot;</span> <span class="n">__version__</span> <span class="o">=</span> <span class="s">&quot;0.3&quot;</span> <span class="k">try</span><span class="p">:</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">time</span> <span class="kn">import</span> <span class="nn">smtplib</span> <span class="kn">import</span> <span class="nn">socket</span> <span class="kn">from</span> <span class="nn">email.mime.text</span> <span class="kn">import</span> <span class="n">MIMEText</span> <span class="kn">from</span> <span class="nn">email.mime.multipart</span> <span class="kn">import</span> <span class="n">MIMEMultipart</span> <span class="kn">from</span> <span class="nn">email.utils</span> <span class="kn">import</span> <span class="n">COMMASPACE</span><span class="p">,</span> <span class="n">formatdate</span> <span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span> <span class="c"># Checks the installation of the necessary python modules </span> <span class="k">print</span><span class="p">((</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">&quot;An error found importing one module:&quot;</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">exc_info</span><span class="p">()[</span><span class="mi">1</span><span class="p">]),</span> <span class="s">&quot;You need to install it&quot;</span><span class="p">,</span> <span class="s">&quot;Stopping...&quot;</span><span class="p">]))</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span> <span class="k">class</span> <span class="nc">Logger</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;</span> <span class="sd"> Create a log object to log script messages.</span> <span class="sd"> These messages can be sended via email or writed in a log file</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Create the object Logger itself and set various attributes.</span> <span class="sd"> These attributes are about the python file wich invokes this module:</span> <span class="sd"> __script_vers = The version of python file which invokes this module</span> <span class="sd"> __script_name = The name of the python file which invokes this module</span> <span class="sd"> filename = the log file&#39;s name</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="kn">from</span> <span class="nn">__main__</span> <span class="kn">import</span> <span class="n">__dict__</span> <span class="k">as</span> <span class="n">__dict</span> <span class="bp">self</span><span class="o">.</span><span class="n">__log</span> <span class="o">=</span> <span class="s">&#39;&#39;</span> <span class="k">if</span> <span class="s">&#39;__version__&#39;</span> <span class="ow">in</span> <span class="n">__dict</span><span class="o">.</span><span class="n">keys</span><span class="p">():</span> <span class="bp">self</span><span class="o">.</span><span class="n">__script_vers</span> <span class="o">=</span> <span class="n">__dict</span><span class="p">[</span><span class="s">&#39;__version__&#39;</span><span class="p">]</span> <span class="k">else</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">__script_vers</span> <span class="o">=</span> <span class="s">&#39;Unknown&#39;</span> <span class="bp">self</span><span class="o">.</span><span class="n">__script_name</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">__dict</span><span class="p">[</span><span class="s">&#39;__file__&#39;</span><span class="p">])</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">&#39;.&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="bp">self</span><span class="o">.</span><span class="n">filename</span> <span class="o">=</span> <span class="s">&#39;{0}.log&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__script_name</span><span class="p">)</span> <span class="k">def</span> <span class="nf">__len__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__log</span><span class="p">)</span> <span class="k">def</span> <span class="nf">__format__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tit</span><span class="p">,</span> <span class="n">cont</span><span class="p">,</span> <span class="n">decor</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Format a block or a list of lines to enhance comprehension.</span> <span class="sd"> (str) tit -- title for the block or list</span> <span class="sd"> (str or iterable) cont -- line/s for the list/block content</span> <span class="sd"> (&#39;=&#39; or &#39;_&#39;) decor - define if it&#39;s list or block and decorate it</span> <span class="sd"> make the looks of self.block() and self.list()</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="n">ending</span> <span class="o">=</span> <span class="p">{</span><span class="s">&#39;=&#39;</span><span class="p">:</span><span class="s">&#39;&#39;</span><span class="p">,</span> <span class="s">&#39;_&#39;</span><span class="p">:</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">}[</span><span class="n">decor</span><span class="p">]</span> <span class="n">end</span> <span class="o">=</span> <span class="p">{</span><span class="s">&#39;=&#39;</span><span class="p">:</span> <span class="s">&#39;=&#39;</span> <span class="o">*</span> <span class="mi">80</span><span class="p">,</span> <span class="s">&#39;_&#39;</span><span class="p">:</span><span class="s">&#39;&#39;</span><span class="p">}[</span><span class="n">decor</span><span class="p">]</span> <span class="n">begin</span> <span class="o">=</span> <span class="s">&#39; &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">tit</span><span class="o">.</span><span class="n">upper</span><span class="p">(),</span> <span class="p">(</span><span class="mi">80</span> <span class="o">-</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">tit</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="o">*</span> <span class="n">decor</span><span class="p">])</span> <span class="o">+</span> <span class="n">ending</span> <span class="n">cont</span> <span class="o">=</span> <span class="p">[</span><span class="n">cont</span><span class="p">]</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">cont</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span> <span class="k">else</span> <span class="n">cont</span> <span class="n">sep</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="bp">self</span><span class="o">.</span><span class="n">__log</span> <span class="o">+=</span> <span class="n">sep</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">begin</span><span class="p">,</span> <span class="n">sep</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">cont</span><span class="p">),</span> <span class="n">end</span><span class="p">,</span> <span class="n">sep</span><span class="p">])</span> <span class="k">def</span> <span class="nf">block</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;A block of text lines headed and followed by a line full of &#39;=&#39;.</span> <span class="sd"> (str) title -- The title that start the first line of &#39;=&#39;</span> <span class="sd"> (str or iterable) content -- The line/s between the &#39;=&#39; lines</span> <span class="sd"> There&#39;s not any empty line between the &#39;=&#39; lines and content, e.g.:</span> <span class="sd"> TITLE ==================================================</span> <span class="sd"> content</span> <span class="sd"> ========================================================</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="k">if</span> <span class="n">content</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">__format__</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="s">&#39;=&#39;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">list</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;A list of text lines headed by a line full of &#39;_&#39;.</span> <span class="sd"> (str) title -- The title that start the line of &#39;_&#39;</span> <span class="sd"> (str or iterable) content -- The line/s after the &#39;_&#39; line</span> <span class="sd"> After the &#39;_&#39; line is a empty line between it and the content, e.g.:</span> <span class="sd"> TITLE __________________________________________________</span> <span class="sd"> content</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="k">if</span> <span class="n">content</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">__format__</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="s">&#39;_&#39;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">free</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Free text unformatted.</span> <span class="sd"> (str) content -- Text free formated</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">__log</span> <span class="o">+=</span> <span class="n">content</span> <span class="o">+</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span> <span class="k">def</span> <span class="nf">time</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;A self.block() formated line with current time and date.</span> <span class="sd"> (str) title -- Title for self.block()</span> <span class="sd"> Looks like this, the data and time are right-justified:</span> <span class="sd"> TITLE ==================================================</span> <span class="sd"> Friday 09/10/10, 20:01:39</span> <span class="sd"> ========================================================</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="bp">self</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="s">&#39;{0:&gt;80}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">&#39;%A </span><span class="si">%x</span><span class="s">, </span><span class="si">%X</span><span class="s">&#39;</span><span class="p">)))</span> <span class="k">def</span> <span class="nf">header</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">msg</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;A self.block() formated header for the log info.</span> <span class="sd"> (str) url -- The url of the script</span> <span class="sd"> (str) msg -- Message to show into the header. To Provide any useful info</span> <span class="sd"> It looks like this:</span> <span class="sd"> SCRIPT =================================================</span> <span class="sd"> script name and version</span> <span class="sd"> url</span> <span class="sd"> msg</span> <span class="sd"> ========================================================</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="n">script</span> <span class="o">=</span> <span class="s">&#39;{0} (ver. {1})&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__script_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">__script_vers</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="s">&#39;Script&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">script</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="p">,</span> <span class="n">msg</span><span class="p">])</span> <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Get the log content.&quot;&quot;&quot;</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">__log</span> <span class="k">def</span> <span class="nf">send</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">subject</span><span class="p">,</span> <span class="n">send_from</span><span class="o">=</span><span class="s">&#39;&#39;</span><span class="p">,</span> <span class="n">dest_to</span><span class="o">=</span><span class="s">&#39;&#39;</span><span class="p">,</span> <span class="n">mail_server</span><span class="o">=</span><span class="s">&#39;localhost&#39;</span><span class="p">,</span> <span class="n">server_user</span><span class="o">=</span><span class="s">&#39;&#39;</span><span class="p">,</span> <span class="n">server_pass</span><span class="o">=</span><span class="s">&#39;&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Send a email with the log.</span> <span class="sd"> Arguments:</span> <span class="sd"> (str) send_from -- a sender&#39;s email address (default &#39;&#39;)</span> <span class="sd"> (str or list) dest_to -- a list of receivers&#39; email addresses (&#39;&#39;)</span> <span class="sd"> (str) subject -- the mail&#39;s subject</span> <span class="sd"> (str) mail_server -- the smtp server (default &#39;localhost&#39;)</span> <span class="sd"> (str) server_user -- the smtp server user (default &#39;&#39;)</span> <span class="sd"> (str) server_pass --the smtp server password (default &#39;&#39;)</span> <span class="sd"> If &#39;send_from&#39; or &#39;dest_to&#39; are empty or None, then script user&#39;s</span> <span class="sd"> mailbox is assumed instead. Useful for loggin scripts</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="n">local_email</span> <span class="o">=</span> <span class="s">&#39;@&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s">&#39;LOGNAME&#39;</span><span class="p">),</span> <span class="n">socket</span><span class="o">.</span><span class="n">gethostname</span><span class="p">()])</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">send_from</span><span class="p">:</span> <span class="n">send_from</span> <span class="o">=</span> <span class="n">local_email</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">dest_to</span><span class="p">:</span> <span class="n">dest_to</span> <span class="o">=</span> <span class="p">[</span><span class="n">local_email</span><span class="p">]</span> <span class="n">dest_to_addrs</span> <span class="o">=</span> <span class="n">COMMASPACE</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dest_to</span><span class="p">)</span> <span class="c"># receivers mails</span> <span class="n">message</span> <span class="o">=</span> <span class="n">MIMEMultipart</span><span class="p">()</span> <span class="n">message</span><span class="p">[</span><span class="s">&#39;Subject&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s">&#39;{0} - {1}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">subject</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">&#39;%A </span><span class="si">%x</span><span class="s">, </span><span class="si">%X</span><span class="s">&#39;</span><span class="p">))</span> <span class="n">message</span><span class="p">[</span><span class="s">&#39;From&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">send_from</span> <span class="n">message</span><span class="p">[</span><span class="s">&#39;To&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">dest_to_addrs</span> <span class="n">message</span><span class="p">[</span><span class="s">&#39;Date&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">formatdate</span><span class="p">(</span><span class="n">localtime</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">message</span><span class="o">.</span><span class="n">preamble</span> <span class="o">=</span> <span class="s">&quot;You&#39;ll not see this in a MIME-aware mail reader.</span><span class="se">\n</span><span class="s">&quot;</span> <span class="n">message</span><span class="o">.</span><span class="n">attach</span><span class="p">(</span><span class="n">MIMEText</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__log</span><span class="p">))</span> <span class="c"># initialize the mail server</span> <span class="n">server</span> <span class="o">=</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTP</span><span class="p">()</span> <span class="c"># Connect to mail server</span> <span class="k">try</span><span class="p">:</span> <span class="n">server</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">mail_server</span><span class="p">)</span> <span class="k">except</span> <span class="n">socket</span><span class="o">.</span><span class="n">gaierror</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;mail error&#39;</span><span class="p">,</span> <span class="s">&#39;Wrong server, are you sure is correct?&#39;</span><span class="p">)</span> <span class="k">except</span> <span class="n">socket</span><span class="o">.</span><span class="n">error</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;mail error&#39;</span><span class="p">,</span> <span class="s">&#39;Server unavailable or connection refused&#39;</span><span class="p">)</span> <span class="c"># Login in mail server</span> <span class="k">if</span> <span class="n">mail_server</span> <span class="o">!=</span> <span class="s">&#39;localhost&#39;</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="n">server</span><span class="o">.</span><span class="n">login</span><span class="p">(</span><span class="n">server_user</span><span class="p">,</span> <span class="n">server_pass</span><span class="p">)</span> <span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPAuthenticationError</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;mail error&#39;</span><span class="p">,</span> <span class="s">&#39;Authentication error&#39;</span><span class="p">)</span> <span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPException</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;mail error&#39;</span><span class="p">,</span> <span class="s">&#39;No suitable authentication method&#39;</span><span class="p">)</span> <span class="c"># Send mail</span> <span class="k">try</span><span class="p">:</span> <span class="n">server</span><span class="o">.</span><span class="n">sendmail</span><span class="p">(</span><span class="n">send_from</span><span class="p">,</span> <span class="n">dest_to_addrs</span><span class="p">,</span> <span class="n">message</span><span class="o">.</span><span class="n">as_string</span><span class="p">())</span> <span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPRecipientsRefused</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;mail error&#39;</span><span class="p">,</span> <span class="s">&#39;All recipients were refused.&#39;</span> <span class="s">&#39;Nobody got the mail.&#39;</span><span class="p">)</span> <span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPSenderRefused</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;mail error&#39;</span><span class="p">,</span> <span class="s">&#39;The server didn’t accept the from_addr&#39;</span><span class="p">)</span> <span class="k">except</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTPDataError</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;mail error&#39;</span><span class="p">,</span> <span class="s">&#39;An unexpected error code, Data refused&#39;</span><span class="p">)</span> <span class="c"># Disconnect from server</span> <span class="n">server</span><span class="o">.</span><span class="n">quit</span><span class="p">()</span> <span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">append</span><span class="o">=</span><span class="bp">False</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Write the log to a file.</span> <span class="sd"> The name of the file will be like this:</span> <span class="sd"> script.log</span> <span class="sd"> where &#39;script&#39; is the name of the script file without extension (.py)</span> <span class="sd"> (boolean) append -- If true appends log to file, else writes a new one</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="n">mode</span> <span class="o">=</span> <span class="s">&#39;ab&#39;</span> <span class="k">if</span> <span class="n">append</span> <span class="k">else</span> <span class="s">&#39;wb&#39;</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">)</span> <span class="k">as</span> <span class="n">log_file</span><span class="p">:</span> <span class="n">log_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">__log</span><span class="p">)</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Main section&quot;&quot;&quot;</span> <span class="n">url</span> <span class="o">=</span> <span class="s">&#39;http://joedicastro.com&#39;</span> <span class="n">head</span> <span class="o">=</span> <span class="s">&#39;This is a test of class Logger&#39;</span> <span class="n">log</span> <span class="o">=</span> <span class="n">Logger</span><span class="p">()</span> <span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">head</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&#39;Start time&#39;</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">block</span><span class="p">(</span><span class="s">&#39;Block&#39;</span><span class="p">,</span> <span class="s">&#39;This is a sample of Logger.block()&#39;</span><span class="o">.</span><span class="n">split</span><span class="p">())</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;List&#39;</span><span class="p">,</span> <span class="s">&#39;This is a sample of Logger.list()&#39;</span><span class="o">.</span><span class="n">split</span><span class="p">())</span> <span class="n">log</span><span class="o">.</span><span class="n">free</span><span class="p">(</span><span class="s">&#39;&#39;&#39;This a sample of logger.free() text.</span> <span class="s">&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque sed</span> <span class="s">tortor eget justo vehicula consequat vel eu quam. Suspendisse non lectus eget</span> <span class="s">orci varius adipiscing.&quot;&#39;&#39;&#39;</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&#39;End time&#39;</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">&#39;This is mail test&#39;</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">free</span><span class="p">(</span><span class="s">&#39;All of this had been recorded in {0}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">filename</span><span class="p">))</span> <span class="k">print</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">get</span><span class="p">())</span> <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span> <span class="n">main</span><span class="p">()</span> </pre></div> <p>Para acceder a la versión más reciente del mismo, acudir a el repositorio <em>Python Recipes</em> en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p> <p>Ejemplos de utilización de este modulo pueden ser <a href="http://joedicastro.com/sincronizar_una_carpeta_local_y_una_remota_a_traves_de_ftp_lftp_mirror">lftp-mirror</a> (incluido dentro del mismo), <a href="http://joedicastro.com/ted_talks_descargar_videos_y_subtitulos_de_las_charlas">TEDTalks</a>, <a href="http://joedicastro.com/generar_informes_de_cambios_en_paquetes_instalados_en_debian_y_ubuntu">dpkg_diff</a> y <a href="http://joedicastro.com/combatir_el_spam_en_drupal">ban_drupal_spammers</a></p>