joe di castrohttp://joedicastro.comTue, 20 Aug 2013 16:53:00 +0200Vim as Python code editorhttp://joedicastro.com/vim-as-python-code-editor.html<p><strong>This article is a translation from the original one in Spanish, <a href="http://joedicastro.com/vim-como-editor-de-codigo-python.html">Vim como editor de codigo Python</a>, published in April 2013.</strong></p> <p>This is an article that I've always wanted to write: "Vim as Python IDE". Two years ago I had a Vim configuration that I thought that was just right for this, even a half-written article. But while I was polishing the config and finishing the article, a bunch of similar articles appeared in the internet and after reading them I realized something: many of them were already obsolete at the time they were published. And I say this because many of them employed plugins that were outdated by the emerge of other fresh and more powerful. In fact in the last two years, the Vim environment has evolved too much that from all the plugins that I used in those days (and the ones pending to test), currently I'm using only 10% of them, the rest are new ones. And this "race" to provide new and more powerful features for Vim continues nowadays, with some great tools.</p> <p>On the other hand, as one is adding plugins and "tuning" his config, there comes a time when you don't know where and what is already mapped, even no remember at all the plugins at features available for you, and which you implemented with so much effort and time.</p> <h2 id="my_configuration">My configuration</h2> <p>Given these two premises I had the idea of kill two birds with one stone: <strong>Document my Vim setup</strong>. What I'm trying with this is that should work for me as sort of cheat-sheet to remember all that I had available in my config, and at the same time to have a coherent mapping and avoid duplicates (at least is the idea). By the other hand, it works also to demonstrate all the Vim's potential to edit Python code and compete with almost any IDE, but with all the advantages of the unique Vim's way.</p> <p>The idea is to have a continuously updated document with respect to my current setup, What better article than this, an always updated one?</p> <p>This is the link to this doc, <code>README.md</code>, included in my dotfiles repository:</p> <ul> <li><a href="https://github.com/joedicastro/dotfiles/tree/master/vim">Doc at GitHub</a></li> </ul> <p><em>English is not my mother tongue, so maybe the article (and the doc) can be full of grammatical mistakes due to my poor English. Sorry for that, I did my best.</em></p>joe di castroTue, 20 Aug 2013 16:53:00 +0200http://joedicastro.com/vim-as-python-code-editor.htmlvimpythoneditoreslinuxVim como editor de codigo Pythonhttp://joedicastro.com/vim-como-editor-de-codigo-python.html<p>Este es un articulo que siempre he deseado escribir: <em>Vim como IDE para Python</em>. Hace dos años tenia una configuración de Vim que creía ideal para esto e incluso el articulo a medio escribir. Pero a medida que iba puliendo la configuración y completando el articulo, empezaron a surgir varios similares en la red (sobre todo en inglés) y caí en la cuenta de algo: muchos ya estaban obsoletos al publicarse. Y cuando digo esto lo digo porque muchos de estos artículos empleaban plugins que se habían quedado desfasados por la aparición de otros mas recientes y potentes. De hecho en los dos últimos años ha cambiado tanto el panorama en el ecosistema en torno a Vim, que de la lista de plugins que empleaba por aquel entonces (y los que tenía pendientes de probar) solo empleo actualmente el 10%, el resto son nuevos. Y esta "carrera" por nutrir a Vim de características nuevas y cada vez más potentes continua hoy en día, con algunas herramientas excelentes.</p> <p>Por otro lado, a medida que uno va añadiendo plugins y "tuneando" su configuración llega un momento que ya no sabes ni donde tienes "mapeados" los atajos, ni recuerdas todas las características que tienes a tu disposición y tanto tiempo empleaste en implantar.</p> <h2 id="mi_configuraci+n">Mi configuración</h2> <p>Partiendo de estas dos premisas se me ocurrió una idea de atajar los dos problemas de un golpe: <strong>Documentar mi configuración</strong>. Lo que trato por un lado es que me sirva personalmente tanto para recordar todo lo que tengo disponible en el editor, como para poder organizar coherentemente todos los atajos y evitar duplicados. Por el otro lado, también me sirve para la idea de demostrar el potencial que tiene Vim a la hora de ser un editor de código que puede estar a la altura de cualquier IDE, pero con todas las ventajas inherentes a la filosofía única de Vim.</p> <p>La idea es que el documento esté permanentemente actualizado a la par que mi configuración real, ¿que mejor articulo que este, que siempre estará actualizado?</p> <p>La configuración está documentada en un <code>README.md</code> dentro de la carpeta de Vim del repositorio de mis dotfiles. Aquí se puede acceder directamente a mi configuración y ver el documento. Actualmente el articulo está en Inglés, aquí se pude acceder a la última revisión en español.</p> <ul> <li><a href="https://github.com/joedicastro/dotfiles/blob/a87b42deb9c1132c8f801bb91d119f0b26d21d68/vim/README.md">Documento en GitHub</a></li> </ul>joe di castroMon, 08 Apr 2013 10:29:00 +0200http://joedicastro.com/vim-como-editor-de-codigo-python.htmlvimpythoneditoreslinuxProductividad & Linux: Rangerhttp://joedicastro.com/productividad-linux-ranger.html<p>Algunos pensarán, de acuerdo, podemos entender que prefieras un <a href="http://joedicastro.com/productividad-en-el-escritorio-linux-tiling.html">gestor de ventanas de mosaico</a>, o que emplees aplicaciones como <a href="http://joedicastro.com/productividad-linux-pentadactyl.html">Pentadactyl</a>, <a href="http://joedicastro.com/productividad-linux-newsbeuter.html">Newsbeuter</a>, <a href="http://joedicastro.com/productividad-linux-zathura.html">Zathura</a> o <a href="http://joedicastro.com/productividad-linux-turses.html">Turses</a>, pero <strong>Ranger</strong>... ya es demasiado. ¿A quien en su sano juicio se le ocurriría utilizar semejante engendro? A mí, y no solo lo utilizo a diario, si no que además opino que es quizás la mejor aplicación que he descubierto en el último año y medio. Y esto último es mucho decir para alguien, que como yo, está continuamente buscando formas de mejorar su modo de trabajo.</p> <p><a href="http://ranger.nongnu.org/">Ranger</a> es un administrador de archivos en modo texto. Pero no es un administrador de archivos en modo texto clásico a dos columnas, como el norton commander y similares. No, Ranger es una pequeña joya con un planteamiento distinto y tan potente, que puede conseguir que olvides que no podías vivir sin un gestor de archivos gráfico como <a href="https://es.wikipedia.org/wiki/Nautilus_(software)">Nautilus</a>, <a href="https://es.wikipedia.org/wiki/Konqueror">Konqueror</a>, <a href="https://es.wikipedia.org/wiki/Dolphin_(administrador_de_archivos)">Dolphin</a> o <a href="https://es.wikipedia.org/wiki/Thunar">Thunar</a>.</p> <h2 id="ranger">Ranger</h2> <p>Tengo que estar enormemente agradecido a su autor, <a href="https://github.com/hut">Roman Zimbelmann</a> no solo por su trabajo, si no por mostrarme que incluso en modo texto, se puede revolucionar el mundo de la usabilidad. Evidentemente, en una aplicación que basa su control en el teclado (tiene un soporte muy básico del ratón), es requisito imprescindible invertir algún tiempo en aprender a utilizarlo, ya que no es tan intuitivo como una aplicación gráfica. A menos que seas un usuario típico de <a href="https://es.wikipedia.org/wiki/Vim">Vim</a>, entonces te encontrarás como en casa. Una vez que dominas esta aplicación, el moverse entre los directorios y el realizar operaciones se hace a velocidad de vértigo y con control absoluto (siempre que no seas un manazas con el teclado).</p> <p><strong>Ranger</strong> está desarrollado en <strong>Python</strong> y emplea una interfaz ncurses igual que <a href="http://joedicastro.com/productividad-linux-turses.html">Turses</a>. Al contrario de lo que algunos puedan pensar, el estar desarrollado en Python no lo convierte ni en pesado ni en lento, si no que se mueve a velocidad endiablada y consume una cantidad de memoria ridícula comparada con cualquier gestor de archivos gráfico.</p> <p style="text-align:center;"><img src="pictures/ranger.png" width="700" height="437" alt="Ranger" /></p> <p>Aquí puede verse una imagen típica de ranger, visualizando el contenido de un directorio y previsualizando el contenido de un fichero, en este caso un fichero de código python.</p> <h3 id="caracter+sticas">Características</h3> <p>¿Qué se puede hacer con ranger?, bueno, quizás es mejor preguntarse que no se puede hacer con Ranger. Prácticamente todas las funciones a las que estés acostumbrado en un administrador de archivos gráfico se pueden realizar con Ranger. Vamos primero a ver un resumen de sus características:</p> <ul> <li>Soporte de UTF-8 por defecto</li> <li>Visualización en múltiples columnas</li> <li>Previsualización del directorio o fichero seleccionado</li> <li>Operaciones comunes sobre los ficheros. Crear, copiar, borrar, cambiar atributos (<code>chmod</code>), etc...</li> <li>Combinaciones de teclado y consola inspiradas en Vim</li> <li>Autodetección de tipos de fichero y apertura de los mismos con el programa adecuado</li> <li>Permite establecer etiquetas y marcadores sobre archivos y directorios</li> <li>Empleo de pestañas. Nos permite navegar por varios directorios simultáneamente sin necesidad de abrir otra instancia del programa. Además se pueden realizar operaciones entre ellas</li> <li>Muestra una barra de progreso para las operaciones que lo necesitan</li> </ul> <p>Conviene dejar claro que no es un programa para <em>impacientes</em>, hacerse con él y sus innumerables opciones requiere tiempo. Hay que leerse la ayuda del programa y practicar con él. A su vez, si queremos personalizar las opciones o ampliarlo, debemos comprender como funcionan sus archivos de configuración, mejor aún si tenemos conocimientos de Python. La documentación de su web, así como la de sus páginas <em>man</em> está un poco desactualizada, por lo que sacarle todo su jugo requiere algo de paciencia.</p> <h3 id="ejemplos">Ejemplos</h3> <p>Voy a intentar dar algo de visibilidad a algunas de las capacidades que nos ofrece ranger empleando una par de ejemplos que lo ilustren.</p> <p>En el siguiente ejemplo vemos la previsualización que hace ranger en función del tipo de fichero que se trate.</p> <div style="text-align:center"> <iframe src="http://player.vimeo.com/video/62061254?title=0&amp;byline=0&amp;portrait=0" width="700" height="438"> </iframe> <p>Previsualización de archivos en ranger con varios tipos de archivo. Recomiendo ver en alta resolución y a pantalla completa.</p> </div> <p>La previsualización de imágenes en ASCII puede parecer una tontería, pero resulta muy útil a la hora de diferenciar entre distintas imágenes con nombre muy parecido. El fichero en HTML está renderizado en vez de mostrarnos el texto plano, algo más amigable. También se agradece mucho el que los ficheros de código se vean con resaltado de sintaxis.</p> <blockquote> <p>Conviene aclarar que para realizar esta previsualización es necesario tener instalado una serie de pequeñas aplicaciones que se detallan en el sitio de ranger como dependencias.</p> </blockquote> <p>Aquí vemos como se abren automáticamente los programas adecuados en función del tipo de archivo (se puede configurar que aplicaciones emplear y cuales usar en cada momento)</p> <div style="text-align:center"> <iframe src="http://player.vimeo.com/video/62634892?title=0&amp;byline=0&amp;portrait=0" width="700" height="438"> </iframe> <p>Ejecución automática de aplicaciones en función del tipo de archivo. Recomiendo ver en alta resolución y a pantalla completa.</p> </div> <p>En el caso del fichero Torrent, al que no tengo asociada ninguna aplicación en ranger, me abre un comando en el que puedo especificar directamente la aplicación que deseo emplear para gestionarlo. En este caso, ninguna. En el archivo comprimido solo me muestra el contenido del mismo, podría asociarle alguna aplicación, pero ya tengo comandos para manejarlos dentro del propio ranger, algo que veremos a continuación.</p> <h2 id="mi_configuraci+n">Mi configuración</h2> <p>No he personalizado mucho mi configuración de momento, pero quien quiera echarle un vistazo puede hacerlo en mi repositorio dotfiles en <a href="http://github.com/joedicastro/dotfiles">GitHub</a></p> <h3 id="archivos_de_configuraci+n">Archivos de configuración</h3> <p>Para configurar ranger necesitamos editar ciertos archivos, que crearemos por primera vez con el siguiente comando:</p> <div class="codehilite"><pre><span class="nv">$ </span>ranger --copy-config<span class="o">=</span>all </pre></div> <p>qué nos creara los siguientes archivos por defecto en la carpeta <code>~/.config/ranger/</code>:</p> <ul> <li><code>commands.py</code> <em>(python)</em>, en este archivo se configuran los comandos a emplear en la línea de comandos de ranger</li> <li><code>options.py</code> <em>(python)</em>, el fichero de opciones principal de ranger</li> <li><code>rc.conf</code> <em>(texto)</em>, aquí configuramos los atajos de teclado de ranger</li> <li><code>rifle.conf</code> <em>(texto)</em>, para establecer los programas que ejecutaran o abrirán un tipo de archivo en orden de preferencia</li> <li><code>scope.sh</code> <em>(bash)</em>, los programas empleados para previsualizar un determinado tipo de archivo</li> </ul> <p>Adicionalmente, con el uso se añadirán tres ficheros más: <code>bookmarks</code>, <code>history</code> y <code>tagged</code> que guardarán los marcadores, la historia de comandos y las etiquetas respectivamente.</p> <h3 id="gestionar_la_papelera">Gestionar la papelera</h3> <p>Por defecto ranger no contempla la gestión de la papelera de Linux que implementan escritorios como Gnome. Aunque no empleo ningún entorno de escritorio ni gestores de archivos gráficos, si me interesa gestionar la papelera, pues cada vez más aplicaciones la emplean para eliminar los archivos. De hecho en <em>bash</em> y <em>zsh</em> tengo un alias para <code>rm</code> que mueve los archivos a la papelera en vez de eliminarlos.</p> <div class="codehilite"><pre><span class="nb">alias </span><span class="nv">rm</span><span class="o">=</span><span class="s1">&#39;mv --target-directory ~/.local/share/Trash/files&#39;</span> </pre></div> <p>Para gestionar la papelera desde ranger, añado dos atajos de teclado y un comando editando varios archivos de configuración de la siguiente manera:</p> <p><strong>Mandar archivos a la papelera</strong></p> <p>Para mandar a la papelera lo que tengamos seleccionado actualmente, en lugar de eliminarlo directamente, añadimos esta línea al fichero <code>rc.conf</code></p> <div class="codehilite"><pre><span class="c"># move to trash</span> <span class="nb">map</span> <span class="n">DD</span> <span class="n">shell</span> <span class="n">mv</span> <span class="o">-</span><span class="n">t</span> <span class="o">~/.</span><span class="n">local</span><span class="o">/</span><span class="n">share</span><span class="o">/</span><span class="n">Trash</span><span class="o">/</span><span class="n">files</span> <span class="o">%</span><span class="n">s</span> </pre></div> <p>de este modo, cada vez que pulsemos <strong><code>DD</code></strong> el contenido seleccionado es movido al directorio donde tenemos nuestra papelera.</p> <p><strong>Movernos a la papelera</strong></p> <p>Del mismo modo que ranger provee por defecto de atajos de teclado para movernos al directorio <code>home</code>, al directorio raiz, etc, he añadido un atajo de teclado siguiendo el mismo criterio para acceder a nuestra papelera. Añadimos lo siguiente al fichero <code>rc.conf</code></p> <div class="codehilite"><pre><span class="c"># go to trash</span> <span class="nb">map</span> <span class="n">gp</span> <span class="n">cd</span> <span class="o">~/.</span><span class="n">local</span><span class="o">/</span><span class="n">share</span><span class="o">/</span><span class="n">Trash</span><span class="o">/</span><span class="n">files</span> </pre></div> <p>así, pulsando <strong><code>gp</code></strong> vamos directamente a la papelera, estemos donde estemos.</p> <p><strong>Vaciar la papelera</strong></p> <p>Para vaciar la papelera opto por emplear un comando, para ello tenemos que editar otro fichero, <code>commands.py</code>, y añadir lo siguiente</p> <div class="codehilite"><pre><span class="k">class</span> <span class="nc">empty</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;</span> <span class="sd"> :empty</span> <span class="sd"> Empties the trash directory ~/.local/share/Trash/files</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="s">&quot;rm -rf ~/.local/share/Trash/files/{*,.[^.]*}&quot;</span><span class="p">)</span> </pre></div> <p>por lo que vaciar la papelera se convierte en algo tan simple como teclear <strong><code>:empty</code></strong> o para los más cómodos <strong><code>:em↹</code></strong></p> <h3 id="desmontar_una_unidad_con_udiskie">Desmontar una unidad con udiskie</h3> <p>Dada mi "particular" configuración, ya que no empleo ningún escritorio, empleo la herramienta <a href="https://bitbucket.org/byronclark/udiskie">udiskie</a> para automontar unidades de almacenamiento externas, como unidades USB o tarjetas de memoria. El montado se hace de forma automática y solo el desmontado ha de hacerse de forma manual. Ya que no quiero teclear el comando cada vez que desee desmontar una unidad, lo que hago es hacerlo desde ranger. En el fichero <code>rc.conf</code> mapeamos este atajo</p> <div class="codehilite"><pre><span class="c"># umount a drive with udiskie</span> <span class="nb">map</span> <span class="n">un</span> <span class="n">shell</span> <span class="o">-</span><span class="n">d</span> <span class="n">udiskie</span><span class="o">-</span><span class="n">umount</span> <span class="o">%</span><span class="n">d</span><span class="o">/%</span><span class="n">f</span> </pre></div> <p>así para desmontar una unidad inicio ranger (en mi caso <strong><code>Win + F1</code></strong>), me dirijo a las unidades externas montadas, <strong><code>gm</code></strong>, selecciono la unidad deseada y la desmonto, <strong><code>un</code></strong>. Un proceso que me lleva apenas dos segundos, sin abandonar las manos del teclado, compárese con hacerlo con un ratón y un entorno gráfico.</p> <h3 id="expulsar_un_cddvd">Expulsar un CD/DVD</h3> <p>Del mismo modo, en lugar de abrir un terminal y escribir el comando <code>eject</code>, en determinadas circunstancias, por ejemplo al acabar de examinar el contenido de un disco, prefiero hacerlo directamente desde ranger. Para ello en el fichero <code>rc.conf</code> creamos este atajo de teclado</p> <div class="codehilite"><pre><span class="c"># eject a CD-ROM/DVD</span> <span class="nb">map</span> <span class="n">ej</span> <span class="n">shell</span> <span class="o">-</span><span class="n">d</span> <span class="n">eject</span> </pre></div> <h3 id="trabajar_con_archivos_comprimidos">Trabajar con archivos comprimidos</h3> <p>Estos comandos los he sacado de el <a href="https://wiki.archlinux.org/index.php/Ranger">Wiki de Arch Linux</a> y los he adaptado a la versión de ranger que estoy empleando en este momento, la 1.5.5</p> <p><strong>Extraer los ficheros de un archivo comprimido</strong></p> <p>Con el comando <strong><code>:extracthere</code></strong> extraemos el contenido de un archivo/s comprimido que previamente habremos seleccionado para copia (es decir empleando el atajo <strong><code>yy</code></strong>). El contenido se extrae en el directorio en el que nos encontremos actualmente. Es muy comodo para extraer múltiples archivos comprimidos a la vez, si quisieramos descomprimir solo un archivo y en el mismo directorio en el que este se encuentra, podríamos emplear el atajo <strong><code>1l</code></strong></p> <p>Para añadir el comando a ranger, tenemos que insertar esta clase en el fichero <code>commands.py</code></p> <div class="codehilite"><pre><span class="k">class</span> <span class="nc">extracthere</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span> <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot; Extract copied files to current directory &quot;&quot;&quot;</span> <span class="n">copied_files</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">copy</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">copied_files</span><span class="p">:</span> <span class="k">return</span> <span class="k">def</span> <span class="nf">refresh</span><span class="p">(</span><span class="n">_</span><span class="p">):</span> <span class="n">cwd</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">get_directory</span><span class="p">(</span><span class="n">original_path</span><span class="p">)</span> <span class="n">cwd</span><span class="o">.</span><span class="n">load_content</span><span class="p">()</span> <span class="n">one_file</span> <span class="o">=</span> <span class="n">copied_files</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="n">cwd</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">thisdir</span> <span class="n">original_path</span> <span class="o">=</span> <span class="n">cwd</span><span class="o">.</span><span class="n">path</span> <span class="n">au_flags</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;-X&#39;</span><span class="p">,</span> <span class="n">cwd</span><span class="o">.</span><span class="n">path</span><span class="p">]</span> <span class="n">au_flags</span> <span class="o">+=</span> <span class="bp">self</span><span class="o">.</span><span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">1</span><span class="p">:]</span> <span class="n">au_flags</span> <span class="o">+=</span> <span class="p">[</span><span class="s">&#39;-e&#39;</span><span class="p">]</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">copy</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">cut</span> <span class="o">=</span> <span class="bp">False</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">copied_files</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span> <span class="n">descr</span> <span class="o">=</span> <span class="s">&quot;extracting: &quot;</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">one_file</span><span class="o">.</span><span class="n">path</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="n">descr</span> <span class="o">=</span> <span class="s">&quot;extracting files from: &quot;</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">one_file</span><span class="o">.</span><span class="n">dirname</span><span class="p">)</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">CommandLoader</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="s">&#39;aunpack&#39;</span><span class="p">]</span> <span class="o">+</span> <span class="n">au_flags</span> \ <span class="o">+</span> <span class="p">[</span><span class="n">f</span><span class="o">.</span><span class="n">path</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">copied_files</span><span class="p">],</span> <span class="n">descr</span><span class="o">=</span><span class="n">descr</span><span class="p">)</span> <span class="n">obj</span><span class="o">.</span><span class="n">signal_bind</span><span class="p">(</span><span class="s">&#39;after&#39;</span><span class="p">,</span> <span class="n">refresh</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> </pre></div> <p><strong>Crear un archivo comprimido</strong></p> <p>Este comando te permite seleccionar uno o varios ficheros y crear un archivo comprimido al llamar al comando <strong><code>:compress</code></strong>. Este comando te permite (a través del autocompletado) darle un nombre automático a partir del directorio o uno personalizado. Te permite además seleccionar el tipo de compresión en función de la extension empleada.</p> <p>En el fichero <code>commands.py</code> añadimos el siguiente código.</p> <div class="codehilite"><pre><span class="k">class</span> <span class="nc">compress</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span> <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot; Compress marked files to current directory &quot;&quot;&quot;</span> <span class="n">cwd</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">thisdir</span> <span class="n">marked_files</span> <span class="o">=</span> <span class="n">cwd</span><span class="o">.</span><span class="n">get_selection</span><span class="p">()</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">marked_files</span><span class="p">:</span> <span class="k">return</span> <span class="k">def</span> <span class="nf">refresh</span><span class="p">(</span><span class="n">_</span><span class="p">):</span> <span class="n">cwd</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">get_directory</span><span class="p">(</span><span class="n">original_path</span><span class="p">)</span> <span class="n">cwd</span><span class="o">.</span><span class="n">load_content</span><span class="p">()</span> <span class="n">original_path</span> <span class="o">=</span> <span class="n">cwd</span><span class="o">.</span><span class="n">path</span> <span class="n">parts</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">()</span> <span class="n">au_flags</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span> <span class="n">descr</span> <span class="o">=</span> <span class="s">&quot;compressing files in: &quot;</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">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">CommandLoader</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="s">&#39;apack&#39;</span><span class="p">]</span> <span class="o">+</span> <span class="n">au_flags</span> <span class="o">+</span> \ <span class="p">[</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">relpath</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">path</span><span class="p">,</span> <span class="n">cwd</span><span class="o">.</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">marked_files</span><span class="p">],</span> <span class="n">descr</span><span class="o">=</span><span class="n">descr</span><span class="p">)</span> <span class="n">obj</span><span class="o">.</span><span class="n">signal_bind</span><span class="p">(</span><span class="s">&#39;after&#39;</span><span class="p">,</span> <span class="n">refresh</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="k">def</span> <span class="nf">tab</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot; Complete with current folder name &quot;&quot;&quot;</span> <span class="n">extension</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;.zip&#39;</span><span class="p">,</span> <span class="s">&#39;.tar.gz&#39;</span><span class="p">,</span> <span class="s">&#39;.rar&#39;</span><span class="p">,</span> <span class="s">&#39;.7z&#39;</span><span class="p">]</span> <span class="k">return</span> <span class="p">[</span><span class="s">&#39;compress &#39;</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="bp">self</span><span class="o">.</span><span class="n">fm</span><span class="o">.</span><span class="n">thisdir</span><span class="o">.</span><span class="n">path</span><span class="p">)</span> <span class="o">+</span> <span class="n">ext</span> <span class="k">for</span> <span class="n">ext</span> <span class="ow">in</span> <span class="n">extension</span><span class="p">]</span> </pre></div>joe di castroThu, 18 Oct 2012 21:50:00 +0200http://joedicastro.com/productividad-linux-ranger.htmlproductividadlinuxpythonncursesProductividad & Linux: Turseshttp://joedicastro.com/productividad-linux-turses.html<p>Que medio mundo parece estar conectado a <a href="http://twitter.com">Twitter</a> no es ninguna novedad. Y que si sigues a un buen número de personas, el intentar estar al tanto de todo lo que ocurre es una temeridad, tampoco debería sorprender a nadie. De hecho, dado su éxito y el enorme flujo de información que circula por él, se han desarrollado cientos de herramientas para gestionarlo.</p> <p>Desde que cree mi cuenta en twitter, he probado unas cuantas, unas veinte (y solo en Linux, en el resto uso la web). La primera, y la elección más obvia ya que por aquel entonces usaba Ubuntu, fue <strong>Gwibber</strong> . Luego cansado de sus muchos problemas, probé un sinnúmero de aplicaciones, solo merece la pena reseñar una: <a href="http://hotot.org/">Hotot</a>. Es la mejor aplicación gráfica para twitter en Linux que conozco.</p> <p>Pero guiado por el mismo objetivo de <a href="http://joedicastro.com/tag/productividad.html">mejorar la productividad</a> en mis herramientas habituales de trabajo, me lancé a la búsqueda de un cliente de twitter que encajara en la misma filosofía. No hay muchas alternativas, la mayoría, hay que reconocerlo, son demasiado "crudas" incluso para mí, que soy un amante de la consola. Pero entonces dí con una pequeña joya, <a href="http://tyrs.nicosphere.net/">Tyrs</a>, desarrollada por <a href="https://github.com/Nic0">Nicolas Paris</a>. Era una herramienta sencilla, pero que cumplía muy bien con todo lo que buscaba de ella. Pero un buen día, Nicolas, en su afán por mejorarla, empezó a reescribir la herramienta empleando una nueva librería para gestionar el interfaz. Las primeras versiones tenían varios fallos y Nicolas pronto se vio desbordado por una tarea que no le apetecía continuar y a la que no podía dedicar más tiempo. Y <a href="http://www.nicosphere.net/small-projects-life-depends-on-his-owner/">decidió abandonar el proyecto</a>, con la esperanza de que alguien se atreviera a retomarlo. Cuando leí la entrada de su blog no perdí la esperanza del todo, al fin y al cabo, estaba desarrollado en Python, un lenguaje con el que me desenvuelvo. Mientras esperaba que hacer, seguí usando la última versión estable a diario. Pero entonces, apareció el milagro, <strong>Turses</strong></p> <h2 id="turses">Turses</h2> <p><a href="https://github.com/alejandrogomez/">Alejandro Gómez</a>, un usuario de <strong>Tyrs</strong> <a href="http://dialelo.com/Python/turses/2012/03/02/turses-un-cliente-de-twitter-con-interfaz-ncurses.html">se lanzó</a> a crear su propia aplicación basándose en él. Y no solo garantizaba la continuidad del buen trabajo empezado por Nicolas, si no que llegaba lleno de ideas frescas y muchas ganas de hacerlo bien. El propio Nicolas <a href="http://www.nicosphere.net/turses-a-fork-from-tyrs-ncurses-twitter-client/">le felicitó</a> por el trabajo y la iniciativa. A día de hoy, el proyecto se sigue desarrollando, y aunque aún tiene algunas metas marcadas por delante, la aplicación es perfectamente usable en el día a día, de hecho es mi cliente habitual.</p> <p>Como ya se habrá podido deducir, <a href="https://github.com/alejandrogomez/Turses">Turses</a> es un cliente de twitter para la consola con interfaz <a href="https://es.wikipedia.org/wiki/Ncurses">ncurses</a>. Está desarrollado en Python y emplea la librería <a href="http://excess.org/urwid/">Urwid</a> para crear la interfaz en curses. Lo mejor de esta aplicación es que emplea atajos de teclado inspirados en <strong>Vim</strong> y es totalmente controlable desde el teclado. Esto unido a que emplea una interfaz basada en texto, la convierten en la aplicación más ágil de todas las que haya probado. <strong>Hotot</strong> también tiene algunas combinaciones de teclas muy útiles, pero ni se acercan a lo que <strong>Turses</strong> te permite.</p> <p>Aquí se puede ver el aspecto por defecto de Turses</p> <p style="text-align:center;"><img src="pictures/turses.png" width="700" height="290" alt="Turses" /></p> <p>Pero no se acaban ahí las bondades de Turses, tiene algunas características geniales como la gestión dinámica de bufferes (líneas temporales) y de columnas. Demos un repaso a lo que nos permite la aplicación:</p> <ul> <li><strong>Múltiples líneas temporales</strong> (<em>bufferes</em>). Es decir, nos permite consultar los tweets de la gente a la que seguimos, los nuestros, menciones, etc. Es decir, los bufferes habituales, incluidos conversaciones, búsquedas y hashtags. Y podemos tenerlas simultáneamente abiertas y navegar entre ellas muy fácilmente.</li> <li><strong>Múltiples columnas</strong>. En cada columna se sitúa un buffer, y podemos añadir o quitar columnas a voluntad de forma muy sencilla. Es decir, que podemos visualizar un solo buffer de forma predefinida, o podemos ver varios a la vez distribuidos en múltiples columnas.</li> <li><strong>Tweet, Reply, Retweet, Borrar</strong>. Vamos, que permite las operaciones habituales con los tweets. Además se puede hacer un Retweet editando el texto, algo que parece obvio, pero que en algunas aplicaciones no es tan sencillo.</li> <li><strong>Seguir/dejar de seguir</strong> a un usuario. Podemos hacerlo bien a través de un tweet o bien introduciendo el nombre del usuario.</li> <li><strong>Des/Marcar como favorito</strong>.</li> <li><strong>Enviar mensajes directos</strong>.</li> <li><strong>Abrir URLs en un navegador</strong>. Nos permite abrir las direcciones que aparecen en un tweet, así como abrir el propio tweet.</li> <li><strong>Visualizar conversaciones</strong>. Podemos abrir un nuevo buffer con la conversación relacionada con un tweet.</li> <li><strong>Contador de los no leídos</strong> funciona para todos los bufferes y nos permite ponerlo a cero manualmente cuando queremos ignorar algunos no leídos.</li> <li><strong>Búsqueda</strong>. Se puede buscar tanto por usuario como por termino.</li> <li><strong>Ver los tweets de cualquier usuario </strong>.</li> <li><strong>Visualizar el perfil de un usuario</strong>.</li> <li><strong>Totalmente personalizable</strong> y la configuración se guarda en un fichero de texto plano.</li> <li><strong>Múltiples cuentas</strong>, eso sí, una por ejecución.</li> <li><strong>Ayuda en línea</strong> con todas las combinaciones de teclas posibles. Accesible a través de la tecla <strong><code>?</code></strong></li> </ul> <p>Interfaz de Turses mostrando múltiples columnas</p> <p style="text-align:center;"><img src="pictures/turses_2cols.png" width="700" height="285" alt="Turses con multiples columnas" /></p> <p>Y entre las metas que tiene marcadas su autor, nos encontramos con el soporte para listas, streaming, notificaciones emergentes y múltiples sesiones. Estoy seguro de que las acabará incorporando, le sobra capacidad. Aunque he de reseñar que actualmente he contribuido con una porción de código minúscula al proyecto y que tengo la intención de seguir colaborando en todo lo que pueda. Si eres programador Python y te apetece echar una mano, <a href="https://github.com/alejandrogomez/Turses">anímate</a>, Alejandro es muy receptivo y un tío muy majo que estará encantado con toda la ayuda que le podamos dar.</p> <h2 id="mi_configuraci+n">Mi configuración</h2> <p>Si a alguien le puede servir como inspiración mi configuración, esta disponible en <a href="http://github.com/joedicastro/dotfiles">GitHub</a></p> <p>Turses mostrando la información del perfil del autor de un tweet</p> <p style="text-align:center;"><img src="pictures/turses_uinfo.png" width="700" height="429" alt="Turses mostrando la información de un usuario" /></p> <h2 id="alternativas">Alternativas</h2> <p>Solo conozco dos alternativas en la misma línea que merezca la pena reseñar, las demás que he probado no estaban a la altura:</p> <ul> <li> <p><a href="http://www.vim.org/scripts/script.php?script_id=2204">TwitVim</a>, es un plugin para Vim. Funciona fantásticamente bien, eso sí, solo apropiado para usuarios de Vim. La probé un tiempo y me gusto, pero personalmente no me gusta emplear Vim para esta tarea y Turses es bastante más manejable.</p> </li> <li> <p><a href="http://www.floodgap.com/software/ttytter/">TTYtter</a>, está escrito en Perl y no tiene interfaz. Trabaja en la línea de comandos a modo de interprete. Funciona muy bien y también lo usé un tiempo, pero su propio funcionamiento le reste eficiencia comparado con Turses.</p> </li> </ul>joe di castroThu, 21 Jun 2012 22:50:00 +0200http://joedicastro.com/productividad-linux-turses.htmlproductividadlinuxncursespythontwitterProductividad & Linux: Newsbeuterhttp://joedicastro.com/productividad-linux-newsbeuter.html<p>Uno de los pilares de la productividad es, como no, la gestión del tiempo. Otro de los pilares fundamentales es, inexorablemente, el conocimiento. Si pierdes el tiempo en tareas irrelevantes (o directamente procrastinando), tu productividad se resiente irremediablemente. Si no tiene los conocimientos adecuados y suficientes, consumes el tiempo aprendiendo a hacerlo o directamente lo pierdes haciéndolo mal. Hoy en día, rara es la actividad donde la formación continua no sea un requisito indispensable, no ya para mejorar o mantener tu rendimiento, si no simplemente para poder seguir ejerciéndola.</p> <p>Por lo tanto nos vemos condenados a intentar mantenernos al día (y ampliar conocimientos), mientras que procuramos dedicarle el menor tiempo posible para no menoscabar nuestro rendimiento. Tal delicado equilibrio no es poca hazaña en nuestros días. Nos vemos inundados de tal cantidad de información, que el filtrado es la única manera de intentar sobrevivir a esa enorme vorágine de datos a la que nos enfrentamos. Afortunadamente tenemos herramientas. Desde hace muchos años he confiado esta tarea a emplear fuentes <strong>RSS</strong> de calidad y una buena herramienta para gestionarlas.</p> <h2 id="mi_b+squeda_del_cliente_rss_ideal">Mi búsqueda del cliente RSS ideal</h2> <p>He empleado muchas herramientas distintas para esta tarea, siempre intentando tener la más idónea para filtrar muchas fuentes RSS en el menor tiempo posible, sin pasar por alto lo que me interesa conocer. Algunas muy buenas, que usaba cuando aún empleaba Windows como SO principal, ya no existen. En Linux he pasado por las más conocidas (en orden cronológico):</p> <ul> <li> <p><a href="http://liferea.sourceforge.net/">Liferea</a>, era muy buena cuando la deje, lo de mostrar los comentarios en las entradas es algo que no he vuelto a ver en ninguna otra herramienta. Pero después de varios años de uso, la abandoné cuando se había convertido en insufriblemente lenta.</p> </li> <li> <p><a href="http://userbase.kde.org/Akregator">Akregator</a>, no era para mí, nunca acabe encontrándome a gusto con ella. Lo hacía todo medianamente bien, pero no destacaba en nada, pronto la abandoné.</p> </li> <li> <p><a href="http://www.blogbridge.com/">Blogbride</a>, su planteamiento es diferente al resto. Es una buena aplicación y estuve con ella muchos meses. Pero siempre seguí buscando algo más eficiente.</p> </li> <li> <p><a href="http://www.rssowl.org/">RSSOwl</a>, la mejor aplicación para leer RSS para escritorio que he conocido. La he usado durante años. Está construida sobre Eclipse. Ofrece muchas posibilidades de personalización y filtrado. Es muy rápida, pero debido a que depende de Eclipse y java, si manejas un número considerable de fuentes (+1000 en aquella época), se puede volver un poco pesada. Además, si como yo, dejabas muchos artículos para leer en otro momento, la base de datos crecía de tal manera, que podía llegar a ser muy lenta. La abandoné buscando algo aún más ágil y productivo.</p> </li> <li> <p><a href="https://es.wikipedia.org/wiki/Google_Reader">Google Reader</a>, decidí darle una oportunidad. Por aquella época había empezado a usar Read it Later (hoy <a href="http://getpocket.com">Pocket</a>) para guardar aquello que quería leer en otro momento o con más calma. La integración con RIL me obligaba a abandonar el teclado y usar el ratón. Además el rediseño que hizo Google no me convencía. Decidí buscar algo aún más rápido y eficiente.</p> </li> </ul> <p>Durante todo ese tiempo probé muchísimas otras alternativas (incluidos complementos para navegadores web) y no encontraba nada que me valiese. Es muy difícil encontrar una aplicación de este tipo que te permita manejar un gran número de fuentes RSS de forma realmente eficiente. Al final ya estaba decidido a regresar a RSSOwl. Pero acostumbrado a <a href="http://joedicastro.com/productividad-linux-pentadactyl.html">Pentadactyl</a> y Vim y las aplicaciones <a href="https://es.wikipedia.org/wiki/Ncurses">ncurses</a>, decidí buscar algo en esa línea, y lo encontré.</p> <h2 id="newsbeuter">Newsbeuter</h2> <p><a href="http://www.newsbeuter.org/">Newsbeuter</a> es un juego de palabras con la palabra alemana "Wildbeuter" que significa <a href="https://es.wikipedia.org/wiki/Cazador_recolector">cazador-recolector</a>, por lo que Newsbeuter vendría a ser algo así como <em>Cazador/Recolector de Noticias</em>. Newsbeuter es una aplicación para leer fuentes RSS y Atom que utiliza una interfaz tipo ncurses para consola. Quién esté familiarizado con el cliente de correo <a href="https://es.wikipedia.org/wiki/Mutt">Mutt</a> se sentirá cómodo enseguida, ya que se inspira en este. Está programado en C++ y dado que funciona en modo texto, una de sus ventajas es la enorme agilidad que proporciona para moverse entre fuentes y noticias.</p> <p>Un resumen de sus características:</p> <ul> <li>Permite suscribirnos a fuentes RSS y Atom</li> <li>Soporta <a href="https://es.wikipedia.org/wiki/OPML">OPML</a> tanto para importar como para exportar las subscripciones</li> <li>Descarga de podcasts</li> <li>Se pueden configurar todos los atajos de teclado libremente</li> <li>Podemos realizar búsquedas entre todos los artículos descargados. Similar a Vim</li> <li>Es posible crear etiquetas para dividir nuestras subscripciones en categorías y realizar filtrados y búsquedas en función a ellas</li> <li>Se pueden sincronizar las fuentes con Google Reader y <a href="http://tt-rss.org/redmine/">Tiny Tiny RSS</a></li> <li>Podemos configurar el color y las cadenas de texto para personalizar su aspecto</li> <li>Se pueden eliminar de forma automática artículos que no deseemos a través de un <a href="https://en.wikipedia.org/wiki/Kill_file">"killfile"</a></li> <li>Es posible integrar cualquier fuente de datos a través de un flexible sistema de filtros y plugins: Fichero con urls, fichero OPML, fichero OPML online, Google Reader, varios ficheros...</li> <li>Se pueden crear "meta fuentes" empleando un potente lenguaje de consultas</li> <li>Permite crear marcadores a partir de cualquier enlace del articulo empleando una aplicación externa o un script</li> <li>Permite guardar artículos en texto plano</li> <li>Podemos otorgar etiquetas de un solo carácter que el autor denomina "flags" por articulo y varias por articulo. Útiles para emplearlas conjuntamente con los filtros</li> <li>Se pueden definir macros</li> <li>Linea de comandos para poder ejecutar comandos y cambiar opciones sobre la marcha</li> <li>Funciona en Linux, Mac OS y FreeBSD</li> <li>Programado en C++ y guarda los artículos en una BDD <a href="http://sqlite.org/">SQLite</a></li> <li><a href="http://newsbeuter.org/doc/newsbeuter.html">Documentación</a> bastante completa</li> </ul> <p>Pantalla con la configuración por defecto de Newsbeuter</p> <p style="text-align:center;"><img src="pictures/newsbeuter_default.png" width="700" height="410" alt="Newsbeuter" /></p> <h2 id="lo_que_lo_distingue">Lo que lo distingue</h2> <p>Muchas de estas características las comparte con otras aplicaciones de las mencionadas antes, pero lo que realmente distingue a Newsbeuter de todas ellas es lo siguiente:</p> <ul> <li>Totalmente controlable desde el teclado, lo que unido a su velocidad, le proporciona una agilidad inigualable.</li> <li>Basado en texto, lo que nos evita distraernos de lo importante, el contenido.</li> <li>Consumo ridículo de memoria y recursos comparado con cualquiera aplicación mencionada antes. En el peor de los casos, me ha consumido unos 30 MiB de RAM (para la versión de 64 bits)</li> <li>Se puede configurar el números de hilos de proceso para descargar noticias. En mi caso, con 8 hilos, tarda unos 40s en leer unos 250 canales RSS. Solo Google Reader por su funcionamiento, puede mejorar esto.</li> <li>Configurable con un fichero de texto plano</li> </ul> <p>Como dijo <a href="http://www.zedshaw.com/essays/i_want_the_mutt_of_feed_readers.html">Zed Shaw</a> y refrenda el autor de la aplicación, <a href="http://synflood.at/">Andreas Krennmair</a>,</p> <blockquote> <p>"Newsbeuter es el Mutt de los lectores de noticias RSS"</p> </blockquote> <p>Desde mi propia experiencia puedo decir que este programa ha cambiado mi forma de leer las <em>noticias del día</em>. Antes, para mi, era muy importante que el lector de noticias que empleara, utilizara un estilo homogéneo entre todas las fuentes y artículos, con el fin de centrarme en el contenido y no perder el tiempo con nimiedades. Es algo más importante de lo que pueda parecer a simple vista, cuando quieres emplear el menor tiempo posible en adquirir la información y al mismo tiempo quieres asimilar lo que lees. Si cuando vas a leer las noticias tienes 45 minutos y 300 artículos sin leer, el cambiar de un articulo con fondo negro y letra Sans Serif mediana a uno con fondo blanco y letra Serif enorme, te supone una distracción y una adaptación de la vista innecesarias e incomodas.</p> <p>Vista de un articulo con mi configuración</p> <p style="text-align:center;"><img src="pictures/newsbeuter_articulo.png" width="700" height="843" alt="Articulo en Newsbeuter" /></p> <p>Pero al comenzar a emplear Newsbeuter me di enseguida cuenta de algo, el carecer por completo de imágenes y compartir cabecera entre todos los artículos, me ha servido para pasar por alto o leer en diagonal aquello que menos me interesa. No solo filtro más rápido, si no que lo hago más eficientemente. Evidentemente hay artículos en los que las imágenes complementan necesariamente al articulo. En estos casos o bien la abro directamente en el navegador (tan sencillo como pulsar <strong><code>o</code></strong> ) o bien puedo abrir las imágenes de forma independiente en el mismo si me interesa por ejemplo ver solo una. En este sentido, Newsbeuter proporciona una lista de todas las Urls presentes en el articulo en forma de lista al final del mismo, pudiendo abrir cualquiera de ellas introduciendo el indice de la misma. De hecho, empleando el comando <strong><code>u</code></strong> podemos acceder a la lista completa de las mimas en una nueva ventana.</p> <h2 id="mi_configuraci+n">Mi configuración</h2> <p>Mi configuración no tiene demasiado de especial, quizás que emplea una combinación de colores distinta a la habitual y que empleo un par de scripts para las notificaciones y para crear marcadores. Esta se puede encontrar en mi repositorio de mis <em>dotfiles</em> en <a href="http://github.com/joedicastro/dotfiles">GitHub</a></p> <h2 id="notificaciones">Notificaciones</h2> <p>Para las notificaciones que emite Newsbeuter después de refrescar las noticias empleo el script <code>notify.py</code> que comentaba en este <a href="http://joedicastro.com/notificaciones-de-escritorio-en-ubuntu-desde-python.html">articulo</a> ligeramente modificado para trabajar con Newsbeuter. En la imagen se puede ver una notificación del programa.</p> <p style="text-align:center;"><img src="pictures/newsbeuter_notify.png" width="487" height="68" alt="Notificación de Newsbeuter" /></p> <div class="codehilite"><pre><span class="k">try</span><span class="p">:</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="nn">gtk</span> <span class="kn">import</span> <span class="nn">pynotify</span> <span class="kn">import</span> <span class="nn">textwrap</span> <span class="n">NOT_NOTIFY</span> <span class="o">=</span> <span class="bp">False</span> <span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span> <span class="n">NOT_NOTIFY</span> <span class="o">=</span> <span class="bp">True</span> <span class="k">def</span> <span class="nf">notify</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">icon</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">wrap</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Send notification icon messages through libnotify.</span> <span class="sd"> Parameters:</span> <span class="sd"> (str) title -- The notification title</span> <span class="sd"> (str) msg -- The message to display into notification</span> <span class="sd"> (str / uri) icon -- Type of icon (ok|info|error|warm|ask|sync) or icon file</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="k">if</span> <span class="n">NOT_NOTIFY</span><span class="p">:</span> <span class="k">return</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">pynotify</span><span class="o">.</span><span class="n">is_initted</span><span class="p">():</span> <span class="n">pynotify</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">title</span><span class="p">)</span> <span class="n">gtk_icon</span> <span class="o">=</span> <span class="p">{</span><span class="s">&#39;ok&#39;</span><span class="p">:</span> <span class="n">gtk</span><span class="o">.</span><span class="n">STOCK_YES</span><span class="p">,</span> <span class="s">&#39;info&#39;</span><span class="p">:</span> <span class="n">gtk</span><span class="o">.</span><span class="n">STOCK_DIALOG_INFO</span><span class="p">,</span> <span class="s">&#39;error&#39;</span><span class="p">:</span> <span class="n">gtk</span><span class="o">.</span><span class="n">STOCK_DIALOG_ERROR</span><span class="p">,</span> <span class="s">&#39;warm&#39;</span><span class="p">:</span> <span class="n">gtk</span><span class="o">.</span><span class="n">STOCK_DIALOG_WARNING</span><span class="p">,</span> <span class="s">&#39;ask&#39;</span><span class="p">:</span> <span class="n">gtk</span><span class="o">.</span><span class="n">STOCK_DIALOG_QUESTION</span><span class="p">,</span> <span class="s">&#39;sync&#39;</span><span class="p">:</span> <span class="n">gtk</span><span class="o">.</span><span class="n">STOCK_JUMP_TO</span><span class="p">}</span> <span class="k">if</span> <span class="n">wrap</span><span class="p">:</span> <span class="n">msg</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="n">join</span><span class="p">(</span><span class="n">textwrap</span><span class="o">.</span><span class="n">wrap</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">wrap</span><span class="p">))</span> <span class="k">try</span><span class="p">:</span> <span class="n">note</span> <span class="o">=</span> <span class="n">pynotify</span><span class="o">.</span><span class="n">Notification</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">msg</span><span class="p">)</span> <span class="n">helper</span> <span class="o">=</span> <span class="n">gtk</span><span class="o">.</span><span class="n">Button</span><span class="p">()</span> <span class="n">gtk_icon</span> <span class="o">=</span> <span class="n">helper</span><span class="o">.</span><span class="n">render_icon</span><span class="p">(</span><span class="n">gtk_icon</span><span class="p">[</span><span class="n">icon</span><span class="p">],</span> <span class="n">gtk</span><span class="o">.</span><span class="n">ICON_SIZE_BUTTON</span><span class="p">)</span> <span class="n">note</span><span class="o">.</span><span class="n">set_icon_from_pixbuf</span><span class="p">(</span><span class="n">gtk_icon</span><span class="p">)</span> <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span> <span class="n">note</span> <span class="o">=</span> <span class="n">pynotify</span><span class="o">.</span><span class="n">Notification</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">icon</span><span class="p">)</span> <span class="n">note</span><span class="o">.</span><span class="n">show</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">notify</span><span class="p">(</span><span class="s">&#39;Newsbeuter&#39;</span><span class="p">,</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">&#39;/home/joedicastro/.newsbeuter/icon.png&#39;</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> <h2 id="integraci+n_con_pocket">Integración con Pocket</h2> <p>Utilizo <a href="http://getpocket.com">Pocket</a> como nexo de unión entre el navegador y el Newsbeuter para archivar todos aquello artículos que me interesa leer, pero que quiero dejar para otro momento más idóneo. Hubo un tiempo en que empleaba <a href="http://delicious.com/">Delicious</a> para esta tarea, pero me parece más adecuado Pocket.</p> <p>Esto lo consigo empleando el comando para crear marcadores de Newsbeuter y un script en Python creado para ello. Esta es la parte del archivo de configuración que relaciona el comando con el script:</p> <div class="codehilite"><pre><span class="n">bookmark</span><span class="o">-</span><span class="n">cmd</span> &quot;<span class="o">~/</span><span class="p">.</span><span class="n">newsbeuter</span><span class="o">/</span><span class="n">send2ril</span><span class="p">.</span><span class="n">py</span>&quot; </pre></div> <p>El script hace uso de la <a href="https://bitbucket.org/Surgo/ril/src">API Python</a> para Read it Later (como se llamaba anteriormente Pocket) para guardar la url del articulo en mi cuenta de Pocket. Así pulsando <strong><code>Ctrl + b</code></strong> se guarda el marcador en Pocket.</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"> send2ril.py: Send a new url to Read it Later</span> <span class="sd">&quot;&quot;&quot;</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;18/06/2012&quot;</span> <span class="n">__version__</span> <span class="o">=</span> <span class="s">&quot;0.1&quot;</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="nn">readitlater</span> <span class="kn">import</span> <span class="nn">ril_config</span> <span class="kn">as</span> <span class="nn">config</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">api</span> <span class="o">=</span> <span class="n">readitlater</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">RIL_APIKEY</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">RIL_USERNAME</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">RIL_PASSWORD</span><span class="p">)</span> <span class="n">new</span> <span class="o">=</span> <span class="p">[{</span><span class="s">&quot;url&quot;</span><span class="p">:</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">&quot;title&quot;</span><span class="p">:</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]}]</span> <span class="n">api</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">new</span><span class="o">=</span><span class="n">new</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>En el script se importa un modulo, <code>ril_config</code>, que es el que contiene las claves de mi cuenta de Pocket, este modulo sería algo similar a esto (evidentemente los valores son falsos):</p> <div class="codehilite"><pre><span class="c"># ril credentials</span> <span class="n">RIL_APIKEY</span> <span class="o">=</span> <span class="s">&#39;987u1ksjsdfRk54kKLKL34jkjij9945k&#39;</span> <span class="n">RIL_USERNAME</span> <span class="o">=</span> <span class="s">&#39;usuario&#39;</span> <span class="n">RIL_PASSWORD</span> <span class="o">=</span> <span class="s">&#39;ADRKSD-Xk3kj5kjljFl&#39;</span> </pre></div> <p>Por lo tanto para hacerlo funcionar necesitamos crear un fichero <code>ril_config.py</code> con las credenciales de cada uno para Pocket. Los campos <code>RIL_USERNAME</code> y <code>RIL_PASSWORD</code> se corresponden evidentemente con el usuario y la contraseña que tengamos para el servicio. El otro campo, <code>RIL_APIKEY</code> es una clave que podemos obtener en <a href="http://getpocket.com/api/signup/">esta página</a> para registrar nuestra aplicación (en este caso nuestro script) y que pueda acceder de forma autorizada a la API de Pocket.</p> <h3 id="copia_de_seguridad_de_las_urls_de_pocket">Copia de seguridad de las urls de Pocket</h3> <p>Del mismo modo, aprovechando la misma API que empleo en el anterior script, he creado otro script que ejecuto regularmente con cron, que me guarda una copia en mi disco duro con todas las urls que tengo guardadas en Pocket. Vamos, una copia de seguridad, uno nunca sabe cuando este tipo de servicios pueden dejar de funcionar. Estas direcciones las guardo en un fichero con formato <a href="https://es.wikipedia.org/wiki/Org-mode">Org-mode</a></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"> get.py: Get the urls stored in Read it Later &amp; save them in a Org-mode file</span> <span class="sd">&quot;&quot;&quot;</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;18/06/2012&quot;</span> <span class="n">__version__</span> <span class="o">=</span> <span class="s">&quot;0.1&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">readitlater</span> <span class="kn">import</span> <span class="nn">ril_config</span> <span class="kn">as</span> <span class="nn">config</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="kn">import</span> <span class="nn">os</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">api</span> <span class="o">=</span> <span class="n">readitlater</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">RIL_APIKEY</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">RIL_USERNAME</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">RIL_PASSWORD</span><span class="p">)</span> <span class="n">items</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">state</span><span class="o">=</span><span class="s">&quot;unread&quot;</span><span class="p">)</span> <span class="n">lista</span> <span class="o">=</span> <span class="n">items</span><span class="p">[</span><span class="s">&quot;list&quot;</span><span class="p">]</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">&quot;ril_urls.org&quot;</span><span class="p">,</span> <span class="s">&quot;w&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">output</span><span class="p">:</span> <span class="n">output</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s">&quot;* Read It Later URLs&quot;</span> <span class="o">+</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">lista</span><span class="o">.</span><span class="n">items</span><span class="p">():</span> <span class="n">output</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s">&quot;** {0}{1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">k</span><span class="p">[</span><span class="s">&#39;title&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s">&quot;utf8&quot;</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">output</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s">&quot; [[{0}][Enlace]]{1}{1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">k</span><span class="p">[</span><span class="s">&#39;url&#39;</span><span class="p">]</span><span class="o">.</span> <span class="n">encode</span><span class="p">(</span><span class="s">&quot;utf8&quot;</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="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> <h2 id="conclusi+n">Conclusión</h2> <p>Newsbeuter no es para todo el mundo, por supuesto, la gran mayoría considerarían decimonónico el emplear un interfaz de texto en vez de uno gráfico. Muchos incluso llamarían herejía a usar el teclado en vez de el ratón (aunque luego se vuelvan locos con las pantallas táctiles). Lo respeto y lo entiendo, pero para aquellos que aman su tiempo y no están dispuestos a desperdiciarlo, deberían darle una oportunidad a esta aplicación.</p>joe di castroMon, 18 Jun 2012 23:26:00 +0200http://joedicastro.com/productividad-linux-newsbeuter.htmlproductividadlinuxncursesrsspythonMantener la temperatura adecuada en un portátil Dell con Linuxhttp://joedicastro.com/mantener-la-temperatura-adecuada-en-un-portatil-dell-con-linux.html<p>Los que tengáis o hayáis tenido un portátil <strong>Dell</strong>, sabréis que es posible controlar la velocidad de funcionamiento de sus ventiladores de forma manual. Existen aplicaciones para Windows, pero también es posible hacerlo desde Linux con el paquete <code>i8kutils</code> creado por Massimo Dal Zotto. Este paquete incluye un modulo del kernel <code>i8k</code> que necesita ser cargado al inicio y una serie de utilidades para controlar el ventilador e informar de la temperatura y otros valores de la <a href="https://es.wikipedia.org/wiki/Bios">BIOS</a>.</p> <p>Este paquete fue creado originalmente para el portátil Dell Inspiron 8000, de ahí su nombre. Esta utilidad aprovecha el modo <a href="http://es.wikipedia.org/wiki/Modo_de_Gerencia_del_Sistema">SMM </a> de la BIOS que estaba presente en los modelos Inspiron para controlar la velocidad de los ventiladores (algo que también permiten otras marcas como Toshiba y Lenovo, para las que también hay algunas utilidades). Dell también ofrece el soporte de SMM BIOS en otras gamas y portátiles, como los de la serie Latitude o XPS. Aunque está utilidad no funciona en algunos modelos.</p> <p>Esta utilidad funciona bajo la línea de comandos, pero al amparo del modulo del kernel surgieron varias aplicaciones, principalmente applets para el panel de Gnome 2, que permitían un control gráfico de los ventiladores y su temperatura. Este control podía ser automático o manual. Yo personalmente me decanté en su momento por emplear <a href="http://conky.sourceforge.net/">Conky</a> para mostrar las velocidades y la temperatura y emplear un script, <code>i8kapplet</code> por Wheelspin, para controlar automáticamente la temperatura dentro de unos rangos.</p> <h2 id="i8kfanspy">i8kfans.py</h2> <p>Este era un script bash que lleva dándome servicio mucho años (el portátil ya tiene sus siete años) pero que por la forma que tiene de controlar cuando se debe subir/bajar la velocidad de los ventiladores, provocaba que se sucediesen de vez en cuando continuos acelerones y frenazos en los mismos. Esto al principio no me disgustaba, pero con los años los ventiladores hacen cada vez más ruido y si bien el sonido constante a alta velocidad es ligeramente molesto, esos cambios bruscos de velocidad se me han vuelto insoportables. Y dado que el portátil se acerca al final de su vida útil, sustituir los ventiladores, aunque es la solución adecuada, no lo veo económicamente rentable.</p> <p>Yo sabía que el problema por el que esto sucedía es porque la BIOS por defecto regula las temperaturas de los ventiladores en unos rangos predefinidos y esto no se desactiva, de hecho trabaja conjuntamente con el script. Como los rangos de temperatura que yo predefino son inferiores a los de la BIOS, en algunas ocasiones los dos pelean por el control de los ventiladores y es lo que ocasiona el problema. Pero el calor es el peor enemigo de la electrónica, y en los portátiles esto es un factor critico. De hecho estoy seguro de que este equipo (muy bien amortizado) me ha durado tantos años gracias a que me he preocupado de este punto. Estoy cansado de ver morir a portátiles y discos duros en verano porque la gente no se preocupa de este tema. Por favor, limpiad el polvo de los ventiladores al llegar el verano, os ahorrareis muchos disgustos.</p> <p>Así que descartado el reemplazar los ventiladores, me planteé el crear un script que intentara hacer lo mismo pero de forma más suave, intentado reducir el número de cambios bruscos de velocidad y fruto de ello es el siguiente script, que está disponible en <a href="http://github.com/joedicastro/i8kfans">GitHub</a>:</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"> i8kfans.py: Adjust the fans speed in various Dell laptops (with a nvidia</span> <span class="sd"> graphics card) to maintain the right temperatures. This affect both fans,</span> <span class="sd"> the cpu and the gpu fan. Originally i8k was created to run in a Dell</span> <span class="sd"> Inspiron 8000 laptop, but this Dell fan control via SMM BIOS is available</span> <span class="sd"> in others laptops of various series (Inspiron, XPS, Latitude, etcetera),</span> <span class="sd"> but not all of them are supported. Mine is an Inspiron 9400 but I tested</span> <span class="sd"> this successfully in a XPS m1330 too.</span> <span class="sd"> Based on a 2006 bash script by Wheelspin, `i8kapplet`. This old script</span> <span class="sd"> served faithfully me for many years, but my ears couldn&#39;t stand much longer</span> <span class="sd"> its random and common slow downs/speed ups. Over the years, fans have</span> <span class="sd"> become more and more loud. This new script runs in a more smooth way, with</span> <span class="sd"> less sudden changes. It&#39;s cheaper than replace booth fans, don&#39;t you</span> <span class="sd"> think?</span> <span class="sd"> This script needs the `i8kutils` linux package installed and the `i8k`</span> <span class="sd"> kernel module loaded to work.</span> <span class="sd">&quot;&quot;&quot;</span> <span class="c">#==============================================================================</span> <span class="c"># Copyright 2012 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;12/06/2012&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">from</span> <span class="nn">os</span> <span class="kn">import</span> <span class="n">linesep</span> <span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">check_output</span><span class="p">,</span> <span class="n">Popen</span><span class="p">,</span> <span class="n">PIPE</span> <span class="kn">from</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="nb">exit</span><span class="p">,</span> <span class="n">exc_info</span> <span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">sleep</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">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">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="nb">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span> <span class="k">def</span> <span class="nf">check_execs</span><span class="p">(</span><span class="o">*</span><span class="n">progs</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Check if the programs are installed, if not exit and report.&quot;&quot;&quot;</span> <span class="k">for</span> <span class="n">prog</span> <span class="ow">in</span> <span class="n">progs</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="n">Popen</span><span class="p">([</span><span class="n">prog</span><span class="p">,</span> <span class="s">&#39;--help&#39;</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span> <span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span> <span class="n">msg</span> <span class="o">=</span> <span class="s">&#39;The {0} program is necessary to run this script&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">prog</span><span class="p">)</span> <span class="nb">exit</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="k">return</span> <span class="k">def</span> <span class="nf">get_right_fan_speed</span><span class="p">(</span><span class="n">current_temperature</span><span class="p">,</span> <span class="n">current_fan_speed</span><span class="p">,</span> <span class="n">temp_triggers</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Get the right fan speed to use with i8kfan command.</span> <span class="sd"> :current_temperature: current temperature value for the fan implied</span> <span class="sd"> :current_fan_speed: current fan speed</span> <span class="sd"> :temp_triggers: the threshold temperatures to trigger the fan speed change</span> <span class="sd"> :returns: right fan speed or &quot;-&quot; (means change nothing to i8kfan)</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="n">right_fan_speed</span> <span class="o">=</span> <span class="bp">None</span> <span class="c"># the right fan speed for the current temp</span> <span class="k">if</span> <span class="n">current_temperature</span> <span class="o">&gt;=</span> <span class="n">temp_triggers</span><span class="p">[</span><span class="mi">0</span><span class="p">]:</span> <span class="k">if</span> <span class="n">current_temperature</span> <span class="o">&gt;=</span> <span class="n">temp_triggers</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">right_fan_speed</span> <span class="o">=</span> <span class="mi">2</span> <span class="k">else</span><span class="p">:</span> <span class="n">right_fan_speed</span> <span class="o">=</span> <span class="mi">1</span> <span class="k">else</span><span class="p">:</span> <span class="n">right_fan_speed</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">return</span> <span class="n">right_fan_speed</span> <span class="k">if</span> <span class="n">right_fan_speed</span> <span class="o">!=</span> <span class="n">current_fan_speed</span> <span class="k">else</span> <span class="s">&quot;-&quot;</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="c"># time between temperature checks</span> <span class="n">interval</span> <span class="o">=</span> <span class="mi">1</span> <span class="c"># the temp thresholds to jump to a faster fan speed. Values greater than</span> <span class="c"># [g|c]pu[0] set the fan speed to 1 and the ones greater than [g|c]pu[1]</span> <span class="c"># set the speed to 2. Obviously, values minor than [g|c]pu[0] stop the fan</span> <span class="n">gpu_temps</span> <span class="o">=</span> <span class="p">[</span><span class="mi">45</span><span class="p">,</span> <span class="mi">53</span><span class="p">]</span> <span class="n">cpu_temps</span> <span class="o">=</span> <span class="p">[</span><span class="mi">40</span><span class="p">,</span> <span class="mi">50</span><span class="p">]</span> <span class="c"># check if the i8k kernel module is already loaded</span> <span class="k">if</span> <span class="s">&quot;i8k&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">check_output</span><span class="p">(</span><span class="s">&quot;ls /proc/&quot;</span><span class="o">.</span><span class="n">split</span><span class="p">()):</span> <span class="nb">exit</span><span class="p">(</span><span class="s">&quot;The i8k kernel module is not loaded&quot;</span><span class="p">)</span> <span class="k">while</span> <span class="bp">True</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="c"># get current values</span> <span class="n">cpu_temp</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">check_output</span><span class="p">(</span><span class="s">&quot;i8kctl temp&quot;</span><span class="o">.</span><span class="n">split</span><span class="p">()))</span> <span class="n">gpu_out</span> <span class="o">=</span> <span class="n">check_output</span><span class="p">(</span><span class="s">&quot;nvidia-smi -q -d TEMPERATURE&quot;</span><span class="o">.</span><span class="n">split</span><span class="p">())</span> <span class="n">gpu_temp</span> <span class="o">=</span> <span class="nb">int</span><span class="p">([</span><span class="n">s</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">gpu_out</span><span class="o">.</span><span class="n">split</span><span class="p">()</span> <span class="k">if</span> <span class="n">s</span><span class="o">.</span><span class="n">isdigit</span><span class="p">()][</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="n">cpu_fan</span><span class="p">,</span> <span class="n">gpu_fan</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">check_output</span><span class="p">(</span><span class="s">&quot;i8kfan&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">()]</span> <span class="c"># get the right speed values for each fan</span> <span class="n">cpu_rfs</span> <span class="o">=</span> <span class="n">get_right_fan_speed</span><span class="p">(</span><span class="n">cpu_temp</span><span class="p">,</span> <span class="n">cpu_fan</span><span class="p">,</span> <span class="n">cpu_temps</span><span class="p">)</span> <span class="n">gpu_rfs</span> <span class="o">=</span> <span class="n">get_right_fan_speed</span><span class="p">(</span><span class="n">gpu_temp</span><span class="p">,</span> <span class="n">gpu_fan</span><span class="p">,</span> <span class="n">gpu_temps</span><span class="p">)</span> <span class="c"># if any of the fans needs to change their speed, change it!</span> <span class="k">if</span> <span class="n">cpu_rfs</span> <span class="o">!=</span> <span class="s">&quot;-&quot;</span> <span class="ow">or</span> <span class="n">gpu_rfs</span> <span class="o">!=</span> <span class="s">&quot;-&quot;</span><span class="p">:</span> <span class="n">Popen</span><span class="p">(</span><span class="s">&quot;i8kfan {0} {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">cpu_rfs</span><span class="p">,</span> <span class="n">gpu_rfs</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(),</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span> <span class="c"># wait a moment. We want a cooler laptop, aren&#39;t we?</span> <span class="n">sleep</span><span class="p">(</span><span class="n">interval</span><span class="p">)</span> <span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span> <span class="nb">exit</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">check_execs</span><span class="p">(</span><span class="s">&quot;i8kctl&quot;</span><span class="p">,</span> <span class="s">&quot;i8kfan&quot;</span><span class="p">,</span> <span class="s">&quot;nvidia-smi&quot;</span><span class="p">)</span> <span class="n">main</span><span class="p">()</span> <span class="c">###############################################################################</span> <span class="c"># Changelog #</span> <span class="c">###############################################################################</span> <span class="c">#</span> <span class="c"># 0.3:</span> <span class="c">#</span> <span class="c"># * Better documentation</span> <span class="c">#</span> <span class="c"># 0.2:</span> <span class="c">#</span> <span class="c"># * Fix an error in a function docstring due to refactorization</span> <span class="c"># * Give appropriate credit to original idea&#39; script</span> <span class="c">#</span> <span class="c"># 0.1:</span> <span class="c">#</span> <span class="c"># * First attempt</span> <span class="c">#</span> </pre></div> <p>De momento, aunque aún sufre del mismo problema inevitable por el conflicto por el control entre el script y la BIOS, de momento he observado que se produce con menos frecuencia y durante menos tiempo. Aunque quizás realice algunos cambios para intentar reducir aún más esos conflictos.</p>joe di castroTue, 12 Jun 2012 10:44:00 +0200http://joedicastro.com/mantener-la-temperatura-adecuada-en-un-portatil-dell-con-linux.htmllinuxdellpythonProductividad en el escritorio Linux: Awesomehttp://joedicastro.com/productividad-en-el-escritorio-linux-awesome.html<p>Como había comentando en el anterior articulo sobre <a href="http://joedicastro.com/productividad-en-el-escritorio-linux-xmonad.html">Xmonad</a>, tenía la intención de probar un <a href="http://joedicastro.com/productividad-en-el-escritorio-linux-tiling.html">Gestor de ventanas de mosaico</a> más, antes de tomar la decisión final de cual emplear a diario. Y si, <em>y solo si</em>, resultaba ser una mejor alternativa, abandonaría <strong>Xmonad</strong>. Este <strong>tiling window manager</strong> era <a href="http://awesome.naquadah.org/">Awesome</a> y ha conseguido con creces que abandone a Xmonad.</p> <p><strong>Awesome</strong> carece de algunas funcionalidades que me ofrecía Xmonad, pero después de trabajar con él durante seis meses y conseguir tener una configuración a mi medida, solamente hecho en falta una característica que comentare después. Además Awesome me ofrece algunas características que no me ofrecía Xmonad y que valoro lo suficiente como para realizar el cambio sin mirar atrás.</p> <h2 id="awesome_vs_xmonad">Awesome vs Xmonad</h2> <p>En esta tabla podemos ver una comparativa rápida de algunas de las características de ambos gestores de ventanas.</p> <table> <thead> <tr> <th>Característica</th> <th>Awesome</th> <th>Xmonad</th> </tr> </thead> <tbody> <tr> <td><strong>Lenguaje</strong></td> <td>C</td> <td>Haskell</td> </tr> <tr> <td><strong>Extensible</strong></td> <td>Lua</td> <td>Haskell</td> </tr> <tr> <td><strong>Gestión ventanas</strong></td> <td>Automático</td> <td>Automático</td> </tr> <tr> <td><strong>Soporte ratón</strong></td> <td>Muy bueno</td> <td>Básico</td> </tr> <tr> <td><strong>Bandeja sistema (systray)</strong></td> <td>Integrada</td> <td>No</td> </tr> <tr> <td><strong>Paneles</strong></td> <td>Integrado</td> <td>No</td> </tr> <tr> <td><strong>Reinicio en caliente</strong></td> <td>Si</td> <td>Si</td> </tr> <tr> <td><strong>Librería</strong></td> <td>XCB</td> <td>Xlib</td> </tr> <tr> <td><strong>Soporte varios monitores</strong></td> <td>Muy bueno</td> <td>Bueno</td> </tr> <tr> <td><strong>Transparencias</strong></td> <td>Gestor externo</td> <td>Gestor externo</td> </tr> <tr> <td><strong>Integración escritorios</strong></td> <td>Buena</td> <td>Muy buena (Gnome, KDE)</td> </tr> <tr> <td><strong>Desarrollo</strong></td> <td>Muy activo</td> <td>Muy activo</td> </tr> <tr> <td><strong>Estabilidad</strong></td> <td>Estable</td> <td>Estable</td> </tr> <tr> <td><strong>Documentación</strong></td> <td>Buena</td> <td>Buena</td> </tr> </tbody> </table> <h3 id="ventajas">Ventajas</h3> <p>Una de las principales ventajas de Awesome sobre Xmonad es la velocidad de respuesta que ofrece el entorno, que en Awesome es bastante superior a la de Xmonad. Alguna que otra vez, al intentar realizar muchas operaciones consecutivas entre ventanas y escritorios en Xmonad, se me ha quedado colgado y he tenido que reiniciarlo (que se hace en caliente y en segundos, pero no deja de ser algo molesto). En este sentido Awesome solo se me ha colgado un par de veces.</p> <p>Otra de las grandes ventajas es que al configurarse en <a href="https://es.wikipedia.org/wiki/Lua">Lua</a> es bastante más sencillo hacerse con él, ya que es un lenguaje relativamente asequible y carece de la complejidad inicial de <a href="https://es.wikipedia.org/wiki/Haskell">Haskell</a> para aquellos que no conocen la programación funcional. Esto aumenta las posibilidades de personalizarlo para un usuario con conocimientos muy básicos de programación.</p> <p>La configuración por defecto de Awesome también es más completa y amigable que la de Xmonad, partiendo de un esquema tradicional de ventanas flotantes y con menús que nos permiten dar los primeros pasos y poder abandonar la sesión sin llegar a conocer las combinaciones de teclado por defecto. Algo que agradecerán aquellos que se hayan tenido que enfrentar a una configuración por defecto de Xmonad por primera vez. Esto invita a conocer el sistema y no abandonarlo "asustado" por su complejidad. En la imagen se puede ver el aspecto que presenta una sesión de Awesome con la configuración por defecto.</p> <p style="text-align:center;"><img src="pictures/awesome_default.png" width="700" height="525" alt="configuración por defecto de Awesome" /></p> <blockquote> <p>Para acceder al menú podemos bien hacer click sobre el icono de Awesome en la parte superior izquierda o bien con el botón derecho en cualquier parte de la pantalla. También se puede acceder a él con la combinación de teclas <strong><code>Win</code></strong> + <strong><code>w</code></strong></p> </blockquote> <p>Finalmente, el que Awesome tenga integrados aspectos como los paneles, una bandeja del sistema (systray), lanzador de aplicaciones y un soporte muy bueno del ratón facilita el poder emplearlo en solitario, sin un sistema de escritorio tradicional detrás. Y a través de librerías es fácil implementar un sistema de notificaciones, un localizador de ventanas, temas, menús emergentes, ...</p> <h3 id="desventajas">Desventajas</h3> <p>Quizás para mí la mayor desventaja frente a Xmonad y lo único que hecho realmente en falta, es la imposibilidad, a diferencia de Xmonad, de poder regular el tamaño vertical de las ventanas. Es particularmente útil en algunas situaciones y en Xmonad está integrado de manera natural. Pero después de varios meses he aprendido a vivir sin ello.</p> <p>La otra gran desventaja es que el desarrollo aún no ha alcanzado el estadio de madurez de Xmonad, esta sujeto a demasiados cambios y no cuidan demasiado la retrocompatibilidad. Las nuevas versiones frecuentemente obligan a realizar cambios en la configuración para que todo vuelva a funcionar con normalidad.</p> <p>Por otro lado, la instalación en algunas distribuciones puede no ser tan sencilla como la de Xmonad por la falta de soporte a la librería XCB o por necesitar versiones especificas de cairo.</p> <h3 id="diferentes_filosof+as">Diferentes filosofías</h3> <p>Siendo como son ambos, gestores de ventanas de mosaico, difieren particularmente en la manera de entender principalmente dos conceptos:</p> <h4 id="__m+ltiples_escritorios__"><strong>Múltiples escritorios</strong></h4> <p>Aquí Xmonad sigue el concepto tradicional de múltiples escritorios en Linux, soportando por defecto 9 por monitor y asignándoles un número del 1 al 0. Luego una aplicación puede estar, por ejemplo, en el escritorio 4 del monitor 2 y podemos movernos entre escritorios y monitores y mover a su vez las aplicaciones entre ellos.</p> <p>En cambio, Awesome, aunque aparentemente sigue el mismo sistema, y para cualquiera que no quiera profundizar en ello no habrá diferencia alguna, se basa en un concepto diferente. En Awesome cada escritorio es en realidad una <strong>etiqueta</strong> y puede ser tanto un número como un texto. En principio también son 9 por monitor, aunque esto puede ampliarse o reducirse en la configuración. La diferencia principal es que una aplicación puede ser asignada a varias etiquetas simultáneamente y puede por lo tanto ser mostrada en varios <em>escritorios</em> a la vez. Esto permite asignar de forma muy sencilla etiquetas destinadas a una tarea y predefinir ciertas aplicaciones para que sean mostradas en una o más etiquetas. Por ejemplo podemos tener una etiqueta para <code>redes sociales</code> y mostrar allí un navegador, pero al mismo tiempo podemos tener una etiqueta <code>desarrollo</code> en la que también queremos que se muestre el navegador. Por otro lado, también es posible mostrar varias etiquetas al mismo tiempo, lo que nos permite trabajar en dos tareas distintas y bien definidas, cada una en su etiqueta, y en un momento dado, visualizarlas juntas sin perder la asignación de etiquetas original. En este sentido Awesome es mucho más flexible y potente que Xmonad.</p> <h4 id="__m+ltiples_monitores__"><strong>Múltiples monitores</strong></h4> <p>En este caso Xmonad comparte el mismo escritorio en distintos monitores. Por lo tanto si estamos en el escritorio 1 en el monitor 1 y nos movemos al escritorio 2, lo hará en todos los monitores a la vez, lo que también es el comportamiento clásico. Con lo que podemos decir que aunque tenemos 9 escritorios por monitor, se comporta como si tuviéramos 9 para todos los monitores.</p> <p>Awesome, al contrario, emplea 9 etiquetas por monitor independientes entre si. Lo que significa que en este casi si tenemos 9 <em>escritorios</em> reales por cada monitor. Si nos encontramos en el monitor 1 y la etiqueta 1, al cambiar a la etiqueta 2, el resto de monitores permanecerán inalterados en la etiqueta en la que estuvieran previamente. En este sentido Awesome también sigue siendo bastante más flexible y potente que Xmonad. Este comportamiento puede ser alterado mediante la configuración. Hay quienes prefieren un método de trabajo y quienes prefieren el otro, yo soy de los que prefieren el comportamiento de Awesome.</p> <h2 id="awesome_con_gnome">Awesome con Gnome</h2> <p>Actualmente estoy empleando Awesome en solitario, si un escritorio detrás, lo que me obliga a configurar por mi cuenta todos los servicios que estos proporcionan, pero me compensa con la ligereza que me reporta el no emplear un pesado escritorio por debajo. Sin embargo, los primeros meses lo empleé conjuntamente con Gnome en Ubuntu, utilizando solo los componentes básicos del mismo. La forma de configurar una sesión de Awesome con Gnome en Ubuntu (11.10 y 12.04) y en <a href="http://www.archlinux.org">Arch Linux</a>, la distribución que estoy empleando ahora, es a través de los siguientes ficheros:</p> <p><code>/usr/share/applications/awesome.desktop</code></p> <div class="codehilite"><pre><span class="k">[Desktop Entry]</span> <span class="na">Version</span><span class="o">=</span><span class="s">1.0</span> <span class="na">Type</span><span class="o">=</span><span class="s">Application</span> <span class="na">Name</span><span class="o">=</span><span class="s">Awesome</span> <span class="na">Comment</span><span class="o">=</span><span class="s">The awesome launcher!</span> <span class="na">TryExec</span><span class="o">=</span><span class="s">awesome</span> <span class="na">Exec</span><span class="o">=</span><span class="s">awesome</span> </pre></div> <p><code>/usr/share/gnome-session/sessions/awesome.session</code></p> <div class="codehilite"><pre><span class="k">[GNOME Session]</span> <span class="na">Name</span><span class="o">=</span><span class="s">Awesome session</span> <span class="na">RequiredComponents</span><span class="o">=</span><span class="s">gnome-settings-daemon;</span> <span class="na">RequiredProviders</span><span class="o">=</span><span class="s">windowmanager;</span> <span class="na">DefaultProvider-windowmanager</span><span class="o">=</span><span class="s">awesome</span> <span class="na">DesktopName</span><span class="o">=</span><span class="s">GNOME</span> </pre></div> <p><code>/usr/share/xsessions/awesome-gnome-session.desktop</code></p> <div class="codehilite"><pre><span class="k">[Desktop Entry]</span> <span class="na">Name</span><span class="o">=</span><span class="s">Awesome GNOME</span> <span class="na">Comment</span><span class="o">=</span><span class="s">Dynamic window manager</span> <span class="na">TryExec</span><span class="o">=</span><span class="s">/usr/bin/gnome-session</span> <span class="na">Exec</span><span class="o">=</span><span class="s">gnome-session --session=awesome</span> <span class="na">Type</span><span class="o">=</span><span class="s">XSession</span> </pre></div> <p>Evidentemente para esto es necesario instalar los paquetes necesarios para ejecutar Awesome que en el caso de Ubuntu son <code>awesome</code> y <code>awesome-extra</code>:</p> <div class="codehilite"><pre><span class="gp">$</span> sudo apt-get install awesome awesome-extra </pre></div> <p>En Arch, bastaría con instalar el paquete <code>awesome</code>:</p> <div class="codehilite"><pre><span class="gp">$</span> sudo pacman -S awesome </pre></div> <h2 id="mi_configuraci+n">Mi configuración</h2> <p>A diferencia de Xmonad, mi configuración de Awesome no está contenida en un solo fichero, ya que el tema y los diversos plugins están alojados dentro de sus propios directorios. Además el fichero de configuración <code>rc.lua</code> es demasiado extenso (cerca de 1200 lineas) para volcarlo aquí. Así que me limitare a mencionar los aspectos más destacados, la configuración completa queda disponible a través de mi repositorio en <a href="http://github.com/joedicastro/dotfiles">GitHub</a>.</p> <p>Destacar que al principio del fichero incluyo una <em>Cheat Sheet</em> ("chuleta") con todas las combinaciones de teclado y ratón que tengo habilitadas en mi configuración.</p> <h2 id="panel_y_widgets">Panel y Widgets</h2> <p>Awesome permite la creación de varios paneles, generalmente se suelen crear en la parte superior y/o inferior de la pantalla. A mi me gusta disponer del máximo espacio posible para las aplicaciones, así que solo he creado uno superior que además puedo ocultar a conveniencia. De hecho, para determinadas tareas, lo mantengo oculto la mayor parte del tiempo y solo lo muestro cuando me interesa consultar la información que allí se muestra.</p> <p>Aquí se puede ver la apariencia del panel superior y una ampliación del mismo:</p> <p style="text-align:center;"><img src="pictures/wibox_full.png" width="700" height="8" alt="wibox" /></p> <p style="text-align:center;"><img src="pictures/wibox.png" width="600" height="63" alt="Zoom sobre la barra de estado" /></p> <p>En el que se pueden apreciar (de izquierda a derecha y de arriba a abajo) los siguientes widgets:</p> <ul> <li>Icono de Awesome. Proporciona acceso al menú de Awesome</li> <li>Etiquetas. Aquí se puede ver que hay dos activas y que nos encontramos en la segunda, <code>β</code></li> <li>Icono de esquema. Vemos que está seleccionado el esquema <code>tile</code></li> <li>Icono y titulo de la ventana activa. En este caso tenemos una ventana de <em>Gvim</em> y además el icono del final nos indica que es una ventana flotante</li> <li>Pomodoro. Es un widget que me permite seguir el método <a href="http://www.pomodorotechnique.com/">Pomodoro</a> y que indica que quedan 4 minutos y 14 segundos para tomarme un pequeño descanso.</li> <li>MPD. Indica el estado del servidor de música <em>mpd</em>, y en este caso nos indica el autor y el titulo de la canción que se está reproduciendo</li> <li>Velocidad de los ventiladores del sistema</li> <li>Temperaturas de CPU y GPU</li> <li>Uso de las CPUs del microprocesador</li> <li>Porcentaje y cantidad de memoria RAM en uso</li> <li>Velocidad (en Kbs) de bajada y subida de la red.</li> <li>Porcentaje de ocupación en disco de <code>/</code> y <code>/home</code></li> <li>Estado de la batería y tiempo estimado de carga/descarga</li> <li>Uptime. Indica el tiempo que lleva el equipo encendido desde el último reinicio</li> <li>Versión del Kernel Linux en ejecución</li> <li>Volumen del sonido del sistema</li> <li>Fecha y hora</li> </ul> <p>Muchos prefieren añadir iconos o encabezados a todos los widgets para facilitar su identificación, pero yo ya conozco para que es cada uno y prefiero un entorno minimalista que me distraiga lo menos posible. Muchos de estos widgets ejecutan una acción al ser pulsados con un botón del ratón o al pasar por encima de ellos. Por ejemplo al pasar el ratón por encima de la fecha y hora se muestra un calendario y al pulsar con el botón izquierdo sobre el uso de las CPUs, se lanza una ventana con el programa <code>htop</code></p> <h2 id="localizar_aplicaciones">Localizar aplicaciones</h2> <p>Una de las funcionalidades geniales que tenía implementada en Xmonad me permitía conocer todas las ventanas que estaban abiertas y elegir la deseada con una rápida combinación de teclas. Awesome no tiene esto implementado, pero gracias a una librería externa, <strong><a href="https://github.com/bioe007/awesome-revelation">Revelation</a></strong>, podemos emplear un método similar al famoso Exposé del Mac OS y también presente en Compiz. Lo que hace es mostrarnos en la etiqueta que estemos actualmente todas las ventanas abiertas en forma de mosaico y podemos elegir una de ellas para saltar automáticamente a ella (y la etiqueta en la que se encuentre). Aquí podemos ver un screencast de su funcionamiento:</p> <p style="text-align:center;"><img src="pictures/revelation.gif" width="640" height="400" alt="Revelation en funcionamiento" /></p> <p>Es también un método muy rápido y muy visual, al final he acabado prefiriendo este método al que empleaba en Xmonad, ya que en este último, el titulo de las ventanas podía ser muy ambiguo y no ser fácil dar con la ventana adecuada.</p> <h2 id="gesti+n_din+mica_de_etiquetas">Gestión dinámica de etiquetas</h2> <p>Aunque por defecto Awesome define 10 etiquetas por monitor, podemos definir a nuestro antojo el número de ellas que queramos en el fichero de configuración. Podemos optar por un número clásico en los escritorios Linux, cuatro, o podemos crear una para cada tarea concreta más una libre. Por defecto tengo definidas diez, empleando para identificarlas los números en griego:</p> <div class="codehilite"><pre><span class="c1">-- definición de las etiquetas en el fichero de configuración rc.lua</span> <span class="n">tags</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">=</span> <span class="n">awful</span><span class="p">.</span><span class="n">tag</span><span class="p">({</span> <span class="s2">&quot;</span><span class="s">α&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="s">β&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="s">γ&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="s">δ&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="s">ε&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="s">ς&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="s">ζ&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="s">η&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="s">θ&quot;</span><span class="p">},</span> <span class="n">s</span><span class="p">,</span> <span class="n">layouts</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> </pre></div> <p>Sin embargo, en mi configuración empleo una librería externa, <a href="http://git.glacicle.org/eminent">Eminent</a>, que gestiona estas etiquetas de forma dinámica, creándolas y destruyéndolas según sea necesario. Así de entrada solo tenemos una etiqueta, <code>α</code> y solo se creara una segunda etiqueta, <code>β</code> si nos desplazamos a ella. Del mismo modo, cuando eliminamos la última ventana de una etiqueta y la abandonamos, se destruye. La ventaja de esto es que por un lado nos facilita localizar donde estamos más fácilmente y por otro, que en caso de ser necesario, si necesitamos más etiquetas (de las creadas por defecto), las creara de forma automática. De este modo cuando llegamos a la etiqueta <code>θ</code>, si necesitamos otra, creara la <code>10</code> y así en lo sucesivo.</p> <h2 id="lanzar_aplicaciones">Lanzar aplicaciones</h2> <p>Existen dos métodos para realizar esta tarea:</p> <ul> <li>Empleando un prompt que emerge en el panel superior y nos permite escribir el comando que queremos ejecutar y que posee autocompletado. La combinación de teclas por defecto que activa esto es <strong><code>Win</code></strong> + <strong><code>r</code></strong></li> </ul> <p style="text-align:center;"><img src="pictures/awesome_prompt.png" width="585" height="30" alt="prompt en Awesome" /></p> <ul> <li>Empleando <a href="http://awesome.naquadah.org/wiki/Menubar">menubar</a>. Menubar es una librería externa, que será incluida en la próxima versión de Awesome por defecto, que al igual que muchos otros lanzadores de aplicaciones (<em>Gnome Do</em>, <em>Kupfer</em>, <em>Synapse</em>, etc) nos va mostrando las aplicaciones disponibles en función de lo que vayamos tecleando. Se lanza con la combinación de teclas <strong><code>Win</code></strong> + <strong><code>p</code></strong> y en mi caso, esta aparece inmediatamente debajo del panel superior. Aquí se puede ver la menubar en funcionamiento (la tecla <strong><code>Win</code></strong> también es llamada <strong><code>Super</code></strong> ):</li> </ul> <p style="text-align:center;"><img src="pictures/menubar.gif" width="640" height="400" alt="Lanzar aplicaciones con menubar en Awesome wm" /></p> <p>Yo además empleo un tercer método que heredo de Xmonad, y es emplear <a href="http://tools.suckless.org/dmenu/">dmenu</a> para lanzar aplicaciones. <code>dmenu</code> es muy parecido al prompt de Awesome, pero busca para el autocompletado en todo el nombre del comando y no solo al principio. Además muestra todas las coincidencias disponibles y que se pueden seleccionar con las teclas de dirección.</p> <p style="text-align:center;"><img src="pictures/dmenu.gif" width="640" height="400" alt="dmenu en acción" /></p> <h3 id="grabar_v+deos_y_screencasts_del_escritorio">Grabar vídeos y screencasts del escritorio</h3> <p>Adicionalmente, tengo definidas ciertas combinaciones de teclas para lanzar determinadas aplicaciones o procesos con las teclas de función. Dentro de estos atajos de teclado cabe destacar cinco que en lugar lanzar una aplicación, comienzan o terminan un determinado proceso. Estas combinaciones, que van de <strong><code>F8</code></strong> a <strong><code>F12</code></strong>, las utilizo para grabar vídeos y screencasts con gifs animados y son las que he empleado para ilustrar este articulo.</p> <div class="codehilite"><pre><span class="c1">-- Win + F8 Start gif screencast recording LowRes</span> <span class="c1">-- Win + F9 Start gif screencast recording HighRes</span> <span class="c1">-- Win + F10 Stop gif screencast recording</span> <span class="c1">-- Win + F11 Start mkv screencast recording</span> <span class="c1">-- Win + F12 Stop mkv screencast recording</span> <span class="n">awful</span><span class="p">.</span><span class="n">key</span><span class="p">({</span> <span class="n">modkey</span> <span class="p">},</span> <span class="s2">&quot;</span><span class="s">F8&quot;</span><span class="p">,</span> <span class="k">function</span> <span class="p">()</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn_with_shell</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">rm &quot;</span> <span class="o">..</span> <span class="n">home_dir</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">/screencast.gif&quot;</span><span class="p">)</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">ffmpeg -f x11grab -s &quot;</span> <span class="o">..</span> <span class="n">scr_res</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s"> -r 2 -i :0.0 -b:v 500k -pix_fmt rgb24 -y&quot;</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s"> -loop 0 -s 640x400 &quot;</span> <span class="o">..</span> <span class="n">home_dir</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">/animated.gif&quot;</span><span class="p">)</span> <span class="k">end</span><span class="p">),</span> <span class="n">awful</span><span class="p">.</span><span class="n">key</span><span class="p">({</span> <span class="n">modkey</span> <span class="p">},</span> <span class="s2">&quot;</span><span class="s">F9&quot;</span><span class="p">,</span> <span class="k">function</span> <span class="p">()</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn_with_shell</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">rm &quot;</span> <span class="o">..</span> <span class="n">home_dir</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">/screencast.gif&quot;</span><span class="p">)</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">ffmpeg -f x11grab -s &quot;</span> <span class="o">..</span> <span class="n">scr_res</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s"> -r 2 -i :0.0 -b:v 500k -pix_fmt rgb24 -y&quot;</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s"> -loop 0 -s 1440x900 &quot;</span> <span class="o">..</span> <span class="n">home_dir</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">/animated.gif&quot;</span><span class="p">)</span> <span class="k">end</span><span class="p">),</span> <span class="n">awful</span><span class="p">.</span><span class="n">key</span><span class="p">({</span> <span class="n">modkey</span> <span class="p">},</span> <span class="s2">&quot;</span><span class="s">F10&quot;</span><span class="p">,</span> <span class="k">function</span> <span class="p">()</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">killall ffmpeg&quot;</span><span class="p">)</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">convert ephemeral:&quot;</span> <span class="o">..</span> <span class="n">home_dir</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">/animated.gif -fuzz 7% -layers Optimize &quot;</span> <span class="o">..</span> <span class="n">home_dir</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">/screencast.gif&quot;</span><span class="p">)</span> <span class="k">end</span><span class="p">),</span> <span class="n">awful</span><span class="p">.</span><span class="n">key</span><span class="p">({</span> <span class="n">modkey</span> <span class="p">},</span> <span class="s2">&quot;</span><span class="s">F11&quot;</span><span class="p">,</span> <span class="k">function</span> <span class="p">()</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn_with_shell</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">rm &quot;</span> <span class="o">..</span> <span class="n">home_dir</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">/screencast.mkv&quot;</span><span class="p">)</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">ffmpeg -f x11grab -s &quot;</span> <span class="o">..</span> <span class="n">scr_res</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s"> -r 25 -i :0.0 -sameq &quot;</span> <span class="o">..</span> <span class="n">home_dir</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">/screencast.mkv&quot;</span><span class="p">)</span> <span class="k">end</span><span class="p">),</span> <span class="n">awful</span><span class="p">.</span><span class="n">key</span><span class="p">({</span> <span class="n">modkey</span> <span class="p">},</span> <span class="s2">&quot;</span><span class="s">F12&quot;</span><span class="p">,</span> <span class="k">function</span> <span class="p">()</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">killall ffmpeg&quot;</span><span class="p">)</span> <span class="k">end</span><span class="p">)</span> </pre></div> <h2 id="gesti+n_tiempo_con_pomodoro">Gestión tiempo con pomodoro</h2> <p>La <a href="https://es.wikipedia.org/wiki/T%C3%A9cnica_Pomodoro">técnica Pomodoro</a> puede ser muy provechosa en ciertas tareas si se emplea con sentido común y se sigue más o menos a rajatabla. Yo la empleo muy a menudo cuando tengo una tarea concreta en la que quiero centrarme sin distracciones para sacarla adelante y no caer en el error de procrastinar demasiado. Es una combinación ideal entre concentración puntual y alternar distracciones, que impiden a uno "quemarse" por no saber echar el freno a tiempo. Es una técnica muy útil a la hora de programar o de crear un articulo como este, pero que es difícilmente aplicable en tareas que requieren cierta multitarea como son las de administración de sistemas.</p> <p>Existen múltiples aplicaciones, applets, indicadores, etc para aplicar esta técnica, que se basaba originalmente en un simple reloj de cocina. Pero alguien, en su momento pensó, que no era mala idea crear un widget para Awesome que nos permitiera medir los tiempos para aplicarla y creo una librería externa para ello. Yo empleo esta <a href="https://github.com/nikolavp/awesome-pomodoro">librería</a> ligeramente modificada para controlar el tiempo transcurrido.</p> <p>El funcionamiento es muy básico, pulsamos sobre el icono y se activa la cuenta atrás de 25 minutos. Al acabar, una alarma sonora y una notificación emergente, nos avisan de que se ha terminado el tiempo de trabajo y nos merecemos un descanso. Volvemos a pulsar sobre el widget y comienza la cuenta atrás de cinco minutos y se repite el proceso. Si pulsamos con el botón derecho se reinicia el widget.</p> <p style="text-align:center;"><img src="pictures/pomodoro.png" width="613" height="105" alt="notificaciones de pomodoro" /></p> <h2 id="debug_con_xephyr">Debug con Xephyr</h2> <p>Esta es una de las posibilidades que nos permite Awesome, que más nos ayudará a la hora de "trastear" con él, sin temor a "romper" nada. Podemos depurar los errores y realizar pruebas con Awesome con una configuración distinta a la nuestra y en una sesión X distinta a la actual. Esto lo conseguimos ejecutando Awesome dentro de un servidor X, <a href="http://www.freedesktop.org/wiki/Software/Xephyr">Xephyr</a>, que aparecerá dentro de nuestra sesión como una ventana más.</p> <p>Yo he integrado esta posibilidad dentro del menú de Awesome para que sea mucho más sencillo e inmediato el poder probar cambios en un entorno de pruebas.</p> <p style="text-align:center;"><img src="pictures/awesome_menu.png" width="603" height="275" alt="Mi menu de Awesome" /></p> <p>En este vídeo se puede ver el funcionamiento básico de este sistema. Normalmente inicio el entorno de pruebas a pantalla completa, pero que se pueda apreciar mejor la diferencia, he grabado la mayoría del vídeo con una sesión de Xephyr a menor resolución. Lo que se puede ver es como primero inicio una sesión de Awesome dentro de Xephyr con la configuración por defecto. Después la cierro y compruebo la sintaxis Lua de la configuración de test. La notificación me alerta de que el fichero no existe, luego procedo a crearlo (es una copia de la configuración real actual) y vuelvo a comprobar la sintaxis. Una vez que veo que esta es correcta, lanzo Awesome con la configuración de test. Cierro la sesión y procedo a realizar cambios en la configuración de pruebas. Ahora lanzo de nuevo Awesome con esta configuración y compruebo que efectivamente se han realizado los cambios. Dentro de esta sesión vuelvo a editar la configuración de test para deshacer los cambios y después reinicio este Awesome de pruebas. Se puede ver como efectivamente todo vuelve a el estado anterior. Cierro la sesión y compruebo el log de la sesión para ver los mensajes de error. Finalmente abro varias sesiones de Awesome a la vez y una a pantalla completa.</p> <div style="text-align:center"> <iframe src="http://player.vimeo.com/video/43562306" width="700" height="438"> </iframe> <p>Muestra de las posibilidades que ofrecen Xephyr y el script <strong>awdt.py</strong> para poder depurar la configuración de Awesome. Recomiendo ver en alta resolución y a pantalla completa.</p> </div> <p>Para realizar estas acciones programé un script en Python, <code>awdt.py</code>, que incluyo al final del articulo.</p> <p>Una de las ventajas de Awesome con respecto a Xmonad, es que no necesitamos recompilar cada vez que realizamos un cambio en la configuración, basta con editar el fichero, salvar y reiniciarlo. Reiniciar Awesome es algo que lleva menos de un segundo.</p> <h2 id="reinicio_apagado_y_suspensi+n">Reinicio, apagado y suspensión</h2> <p>Dado que no empleo ningún escritorio, arrancando directamente la sesión en Awesome, debo gestionar directamente cierto tipo de situaciones que vienen resueltas de manera predefinida en estos. Uno de estos casos es la gestión de energía del equipo, esto es: apagarlo, reiniciarlo, suspenderlo e hibernarlo. Dado que nunca lo hiberno, he implementado únicamente las tres primeras acciones. Lo que hago es emplear directamente los comandos de consola, que ejecuto a través de un dialogo gráfico que me pide la contraseña para <code>sudo</code> con <code>gksudo</code> (podría haber empleado <em>Zenity</em>, <em>Xdialog</em> o cualquier otro similar) que es activado a través de una combinación de teclas. Por ejemplo, para suspender el equipo lo activo a través de la combinación <strong><code>Win</code></strong> + <strong><code>s</code></strong> :</p> <div class="codehilite"><pre><span class="n">awful</span><span class="p">.</span><span class="n">key</span><span class="p">({</span> <span class="n">modkey</span> <span class="p">},</span> <span class="s2">&quot;</span><span class="s">s&quot;</span><span class="p">,</span> <span class="k">function</span> <span class="p">()</span> <span class="n">awful</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="s2">&quot;</span><span class="s">gksudo pm-suspend -m &quot;</span> <span class="o">..</span> <span class="s2">&quot;</span><span class="s">&#39;Se va a suspender el equipo, ¿estás seguro?&#39;&quot;</span><span class="p">)</span> <span class="k">end</span><span class="p">),</span> </pre></div> <p>con el siguiente resultado:</p> <p style="text-align:center;"><img src="pictures/suspend.png" width="381" height="191" alt="Dialogo para suspensión" /></p> <p>Sé que la mayoría no está acostumbrado a tener que introducir la contraseña para realizar cualquiera de estas acciones y posiblemente detestarían hacerlo, pero yo lo prefiero así.</p> <h2 id="bloqueo_de_la_pantalla">Bloqueo de la pantalla</h2> <p>Empleo un método similar para bloquear la pantalla, ya que no empleo un salvapantallas (simplemente apago la pantalla pasado un cierto tiempo) empleo el programa <a href="http://joelburget.com/slimlock/"><code>slimlock</code></a> para realizar esta tarea y lo activo de forma manual con otra combinación de teclas. El resultado es una pantalla como esta:</p> <p style="text-align:center;"><img src="pictures/slimlock.png" width="642" height="401" alt="slimlock" /></p> <h2 id="documentaci+n_y_enlaces_de_inter+s">Documentación y enlaces de interés.</h2> <p>La documentación del código fuente y de la API de Awesome son bastante buenas, con lo que si tienes los conocimientos suficientes no te será muy dificil hacerte con él. Si necesitas algo más general, tanto el propio Wiki de Awesome como los recursos disponibles gracias a su comunidad (lista de correo e IRC) son bastante útiles para ayudarte a conseguir la configuración que más se ajuste a tus necesidades. Es una comunidad muy dinámica y bastante colaboradora. También en el Wiki de Arch Linux hay información interesante sobre Awesome.</p> <ul> <li><a href="http://awesome.naquadah.org/">Pagina principal</a></li> <li><a href="http://awesome.naquadah.org/wiki/Main_Page">Wiki</a></li> <li><a href="http://awesome.naquadah.org/wiki/Screenshots">Pantallazos</a></li> <li><a href="http://awesome.naquadah.org/wiki/User_Configuration_Files">Ficheros de configuración</a></li> <li><a href="http://awesome.naquadah.org/doc/api/">API Docs</a></li> <li><a href="https://wiki.archlinux.org/index.php/Awesome">Arch Linux Wiki</a></li> <li><a href="http://www.lua.org/pil/">Programming in Lua</a></li> </ul> <h2 id="awdtpy">awdt.py</h2> <p>Este es el contenido del script <code>awdt.py</code> que empleo para depurar la configuración de Awesome en un entorno de pruebas. Se encuentra dentro del mismo repositorio que el resto de la configuración.</p> <div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span> <span class="c"># encoding: utf-8</span> <span class="sd">&quot;&quot;&quot;</span> <span class="sd"> awdt.py: a script to debug awesome wm configs in nested Xephyr sessions</span> <span class="sd"> This script is a tool intended to help to debug Awesome wm configurations</span> <span class="sd"> in a safe manner. To this purpose uses the Xephyr X server to nest a X</span> <span class="sd"> session inside the current Awesome X session.</span> <span class="sd"> The original idea come from the mikar&#39;s bash awmtt script (Thanks mikar):</span> <span class="sd"> https://github.com/mikar/awmtt</span> <span class="sd"> Needs logger.py and check_execs.py from my Python Recipes repository at</span> <span class="sd"> https://github.com/joedicastro/python-recipes</span> <span class="sd">&quot;&quot;&quot;</span> <span class="c">#==============================================================================</span> <span class="c"># Copyright 2012 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;05/06/2012&quot;</span> <span class="n">__version__</span> <span class="o">=</span> <span class="s">&quot;0.5&quot;</span> <span class="k">try</span><span class="p">:</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">from</span> <span class="nn">argparse</span> <span class="kn">import</span> <span class="n">ArgumentParser</span><span class="p">,</span> <span class="n">RawDescriptionHelpFormatter</span> <span class="kn">from</span> <span class="nn">re</span> <span class="kn">import</span> <span class="n">findall</span> <span class="kn">from</span> <span class="nn">shutil</span> <span class="kn">import</span> <span class="n">copy</span> <span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">Popen</span><span class="p">,</span> <span class="n">PIPE</span><span class="p">,</span> <span class="n">STDOUT</span> <span class="kn">from</span> <span class="nn">tempfile</span> <span class="kn">import</span> <span class="n">gettempdir</span> <span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">sleep</span> <span class="kn">from</span> <span class="nn">logger</span> <span class="kn">import</span> <span class="n">Logger</span> <span class="kn">from</span> <span class="nn">check_execs</span> <span class="kn">import</span> <span class="n">check_execs</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">def</span> <span class="nf">arguments</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Defines the command line arguments for the script.&quot;&quot;&quot;</span> <span class="n">main_desc</span> <span class="o">=</span> <span class="s">&quot;&quot;&quot;Debug awesome wm configurations in Xephyr sessions.</span> <span class="s"> Use `new` to create a new test config file cloned from your rc.lua</span> <span class="s"> Use `check` to test the Lua syntax on this file</span> <span class="s"> Use `start` to start a new awesome debug session</span> <span class="s"> Use `restart` to restart all awesome debug sessions</span> <span class="s"> Use `stop` to stop all awesome debug sessions</span> <span class="s"> &quot;&quot;&quot;</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="n">main_desc</span><span class="p">,</span> <span class="n">formatter_class</span><span class="o">=</span><span class="n">RawDescriptionHelpFormatter</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;action&quot;</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s">&quot;new&quot;</span><span class="p">,</span> <span class="s">&quot;check&quot;</span><span class="p">,</span> <span class="s">&quot;start&quot;</span><span class="p">,</span> <span class="s">&quot;restart&quot;</span><span class="p">,</span> <span class="s">&quot;stop&quot;</span><span class="p">],</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;the action to perform&quot;</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;-t&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s">&quot;test&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">&quot;store_true&quot;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;use created test configuration file&quot;</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;-s&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s">&quot;screen&quot;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;the screen resolution&quot;</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;-d&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s">&quot;display&quot;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;the DISPLAY to use&quot;</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;-v&quot;</span><span class="p">,</span> <span class="s">&quot;--version&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">&quot;version&quot;</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s">&quot;</span><span class="si">%(prog)s</span><span class="s"> {0}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">__version__</span><span class="p">),</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;show program&#39;s version number and exit&quot;</span><span class="p">)</span> <span class="k">return</span> <span class="n">parser</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;The script core.&quot;&quot;&quot;</span> <span class="c"># the files needed</span> <span class="n">cfg_dir</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">expanduser</span><span class="p">(</span><span class="s">&quot;~/.config/awesome&quot;</span><span class="p">)</span> <span class="n">rc_real</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">join</span><span class="p">(</span><span class="n">cfg_dir</span><span class="p">,</span> <span class="s">&quot;rc.lua&quot;</span><span class="p">)</span> <span class="n">rc_test</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">join</span><span class="p">(</span><span class="n">cfg_dir</span><span class="p">,</span> <span class="s">&quot;rc_test.lua&quot;</span><span class="p">)</span> <span class="n">rc_original</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">join</span><span class="p">(</span><span class="n">cfg_dir</span><span class="p">,</span> <span class="s">&quot;rc_original.lua&quot;</span><span class="p">)</span> <span class="n">xpids_tmp</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">join</span><span class="p">(</span><span class="n">gettempdir</span><span class="p">(),</span> <span class="s">&quot;xpids&quot;</span><span class="p">)</span> <span class="n">apids_tmp</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">join</span><span class="p">(</span><span class="n">gettempdir</span><span class="p">(),</span> <span class="s">&quot;apids&quot;</span><span class="p">)</span> <span class="n">log_file</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">join</span><span class="p">(</span><span class="n">cfg_dir</span><span class="p">,</span> <span class="s">&quot;awdt.log&quot;</span><span class="p">)</span> <span class="n">args</span> <span class="o">=</span> <span class="n">arguments</span><span class="p">()</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span> <span class="c"># get the current screen resolution</span> <span class="n">xdpyinfo</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="s">&quot;xdpyinfo&quot;</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">read</span><span class="p">()</span> <span class="n">currres</span> <span class="o">=</span> <span class="n">findall</span><span class="p">(</span><span class="s">&quot;dimensions:\s*(\d+x\d+)\spixels&quot;</span><span class="p">,</span> <span class="n">xdpyinfo</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="c"># set defaults</span> <span class="n">args</span><span class="o">.</span><span class="n">screen</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">screen</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">screen</span> <span class="k">else</span> <span class="n">currres</span> <span class="n">args</span><span class="o">.</span><span class="n">test</span> <span class="o">=</span> <span class="n">rc_test</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">test</span> <span class="k">else</span> <span class="n">rc_original</span> <span class="n">args</span><span class="o">.</span><span class="n">display</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">display</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">display</span> <span class="k">else</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">action</span> <span class="o">==</span> <span class="s">&quot;new&quot;</span><span class="p">:</span> <span class="n">copy</span><span class="p">(</span><span class="n">rc_real</span><span class="p">,</span> <span class="n">rc_test</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">action</span> <span class="o">==</span> <span class="s">&quot;check&quot;</span><span class="p">:</span> <span class="n">check</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="s">&quot;awesome -c {0} -k&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">rc_test</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(),</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">STDOUT</span><span class="p">)</span> <span class="n">check_out</span> <span class="o">=</span> <span class="n">check</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span> <span class="n">Popen</span><span class="p">([</span><span class="s">&quot;notify-send&quot;</span><span class="p">,</span> <span class="s">&quot;Lua sintax chek:&quot;</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="n">join</span><span class="p">(</span><span class="n">check_out</span><span class="p">)])</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">action</span> <span class="o">==</span> <span class="s">&quot;start&quot;</span><span class="p">:</span> <span class="c"># clean log in each debug session</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">apids_tmp</span><span class="p">):</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">log_file</span><span class="p">):</span> <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">log_file</span><span class="p">)</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">filename</span> <span class="o">=</span> <span class="n">log_file</span> <span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="s">&quot;https://github.com/joedicastro/dotfiles&quot;</span><span class="p">,</span> <span class="s">&quot;This is a log from an Awesome wm&#39;s debug session&quot;</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">&quot;Start time&quot;</span><span class="p">)</span> <span class="n">x_cmd</span> <span class="o">=</span> <span class="s">&quot;Xephyr -ac -br -noreset -screen {0} :{1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">screen</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">display</span><span class="p">)</span> <span class="n">aw_cmd</span> <span class="o">=</span> <span class="s">&quot;awesome -c {0}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">test</span><span class="p">)</span> <span class="n">xserver</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="n">x_cmd</span><span class="o">.</span><span class="n">split</span><span class="p">(),</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">STDOUT</span><span class="p">)</span> <span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">os</span><span class="o">.</span><span class="n">putenv</span><span class="p">(</span><span class="s">&quot;DISPLAY&quot;</span><span class="p">,</span> <span class="s">&quot;:{0}.0&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">display</span><span class="p">))</span> <span class="n">awesome</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="n">aw_cmd</span><span class="o">.</span><span class="n">split</span><span class="p">(),</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">STDOUT</span><span class="p">)</span> <span class="c"># save the process PIDs for kill them properly later. This way, no</span> <span class="c"># matter how many awesome sessions do you start, all of them will be</span> <span class="c"># reported to the log file. Also, awesome PIDs are used to restart each</span> <span class="c"># one when is required</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">xpids_tmp</span><span class="p">,</span> <span class="s">&#39;a+&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">xpids</span><span class="p">:</span> <span class="n">xpids</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">xserver</span><span class="o">.</span><span class="n">pid</span><span class="p">)</span> <span class="o">+</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">apids_tmp</span><span class="p">,</span> <span class="s">&#39;a+&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">apids</span><span class="p">:</span> <span class="n">apids</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">awesome</span><span class="o">.</span><span class="n">pid</span><span class="p">)</span> <span class="o">+</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</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">&quot;Parameters&quot;</span><span class="p">,</span> <span class="p">[</span><span class="s">&quot;Screen resolution: {0}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">screen</span><span class="p">),</span> <span class="s">&quot;Display: {0}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">display</span><span class="p">),</span> <span class="s">&quot;Configuration file: {0}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">test</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">&quot;Xephyr output&quot;</span><span class="p">,</span> <span class="n">xserver</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">readlines</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">&quot;Awesome output&quot;</span><span class="p">,</span> <span class="n">awesome</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">readlines</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">&quot;End time&quot;</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="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">4</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="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">action</span> <span class="o">==</span> <span class="s">&quot;restart&quot;</span><span class="p">:</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">apids_tmp</span><span class="p">,</span> <span class="s">&#39;r&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">apids</span><span class="p">:</span> <span class="k">for</span> <span class="n">pid</span> <span class="ow">in</span> <span class="n">apids</span><span class="o">.</span><span class="n">readlines</span><span class="p">():</span> <span class="n">os</span><span class="o">.</span><span class="n">kill</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">pid</span><span class="p">),</span> <span class="mi">1</span><span class="p">)</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">action</span> <span class="o">==</span> <span class="s">&quot;stop&quot;</span><span class="p">:</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">xpids_tmp</span><span class="p">,</span> <span class="s">&#39;r&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">xpids</span><span class="p">:</span> <span class="k">for</span> <span class="n">pid</span> <span class="ow">in</span> <span class="n">xpids</span><span class="o">.</span><span class="n">readlines</span><span class="p">():</span> <span class="n">os</span><span class="o">.</span><span class="n">kill</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">pid</span><span class="p">),</span> <span class="mi">9</span><span class="p">)</span> <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">xpids_tmp</span><span class="p">)</span> <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">apids_tmp</span><span class="p">)</span> <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&#39;__main__&#39;</span><span class="p">:</span> <span class="n">check_execs</span><span class="p">(</span><span class="s">&quot;Xephyr&quot;</span><span class="p">,</span> <span class="s">&quot;awesome&quot;</span><span class="p">)</span> <span class="n">main</span><span class="p">()</span> <span class="c">#==============================================================================</span> <span class="c"># Changelog:</span> <span class="c">#==============================================================================</span> <span class="c">#</span> <span class="c"># 0.5:</span> <span class="c">#</span> <span class="c"># * New log file per debug session</span> <span class="c"># * Improve argparse help</span> <span class="c"># * Better comments</span> <span class="c">#</span> <span class="c"># 0.4:</span> <span class="c">#</span> <span class="c"># * remove debug prints</span> <span class="c"># * clean code</span> <span class="c">#</span> <span class="c"># 0.3:</span> <span class="c">#</span> <span class="c"># * implement logging functions</span> <span class="c"># * check for executables</span> <span class="c">#</span> <span class="c"># 0.2:</span> <span class="c">#</span> <span class="c"># * argparse instead of sys.argv</span> <span class="c"># * create a new rc_test.lua file from original</span> <span class="c"># * restart the awesome session</span> <span class="c">#</span> <span class="c"># 0.1:</span> <span class="c">#</span> <span class="c"># * First attempt</span> <span class="c">#</span> <span class="c">#==============================================================================</span> </pre></div>joe di castroThu, 07 Jun 2012 00:00:00 +0200http://joedicastro.com/productividad-en-el-escritorio-linux-awesome.htmllinuxarchtilingtwmproductividadawesomegnomeluapythonSincronizar Bitbucket y GitHubhttp://joedicastro.com/sincronizar-bitbucket-y-github.html<p>Para mis proyectos empleo generalmente <a href="http://mercurial.selenic.com/">mercurial</a> (hg) como sistema de control de versiones, porque está hecho en Python y me parece más elegante y agradable de usar que <a href="http://git-scm.com/">git</a>, aunque empleo git para algunas tareas, como gestionar los plugins de <a href="http://www.vim.org">Vim</a>. Del mismo modo, el emplear hg como <a href="http://es.wikipedia.org/wiki/Programas_para_control_de_versiones">DCVS</a> me llevaba de forma natural a emplear <a href="http://bitbucket.org">Bitbucket</a> como alojamiento para mis repositorios públicos. </p> <p>Siempre me ha gustado <strong>Bitbucket</strong>, su estilo sencillo, pero muy potente y creo que tiene algunas características que son superiores a las de sus rivales (el <a href="http://blog.bitbucket.org/2011/12/08/pull-requests-with-side-by-side-diffs/">diff side-by-side</a>, por ejemplo<sup id="fnref:gt"><a href="#fn:gt" rel="footnote">1</a></sup>). Pero también tengo claro que si hay algún alojamiento de código en la red que destaca sobre todos los demás es <a href="http://github.com">GitHub</a>, "todo" el mundo está allí y de algún modo, estás "obligado" a estar. <strong>GitHub</strong> tiene algunas características muy potentes y en ciertos aspectos es muy superior a Bitbucket, aunque me sigue gustando más el <em>feeling</em> de este último. </p> <h2 id="hg__git">Hg != Git</h2> <p>Me planteé entonces hace unos días que lo mejor era mantener una replica de mis repositorios alojados en Bitbucket en GitHub, como dice el refrán, <em>Nunca tengas todos tus huevos en una misma cesta</em>. El problema es que aunque Bitbucket soporta repositorios tanto en mercurial como en git (para competir con GitHub), GitHub solo soporta repositorios en Git. Y dado el éxito que tienen, dudo mucho que tengan intención alguna de soportar otro sistema de versiones distinto a Git. </p> <p>Técnicamente es posible mantener un repositorio con los dos dcvs a la vez, pero maldita la gracia que me hacía, ademas de que no es nada aconsejable por el incremento de tamaño en disco que esto supondría. Entonces, ¿como hacer para poder alojar un repositorio mantenido con mercurial en un alojamiento que solo soporta Git? La solución, <strong>hg-git</strong>.</p> <h3 id="hg-git">hg-git</h3> <p><a href="http://hg-git.github.com/">hg-git</a> es un plugin para mercurial que permite sincronizar el repositorio local en hg con un repositorio en git, admitiendo tanto <em>push</em> como <em>pull</em> y sin perdidas de información. Gracias a este plugin, podemos alojar el repositorio en los dos sitios a la vez, empleando solo mercurial para gestionarlo.</p> <p>Instalarlo es muy fácil (desde <code>easy_install</code> o <code>pip</code>) y emplearlo también. Primero necesitas habilitarlo en el fichero <code>~/.hgrc</code>, así como la extensión bookmarks que necesita para trabajar.</p> <div class="codehilite"><pre><span class="k">[extensions]</span> <span class="na">hgext.bookmarks</span> <span class="o">=</span> <span class="na">hggit</span> <span class="o">=</span> </pre></div> <p>A continuación tienes que ir a tu repositorio y asignarle un <code>bookmark</code> a la rama que tengas por defecto (suele ser <code>default</code>) o a <code>tip</code> con el nombre de <code>master</code> (el nombre de las ramas por defecto en git), es decir:</p> <div class="codehilite"><pre><span class="gp">$</span> hg bookmark -r default master -f </pre></div> <p>Y luego emplearlo es tan sencillo como si fuera un repositorio de mercurial, por ejemplo un push:</p> <div class="codehilite"><pre><span class="gp">$</span> hg push git+ssh://git@github.com/joedicastro/joedicastro.com.git </pre></div> <h2 id="sincronizar_el_repositorio_con_bitbucket_y_github">Sincronizar el repositorio con Bitbucket y GitHub</h2> <p>Ahora, lo que tampoco me apetecía era tener que andar haciendo un push cada vez para cada alojamiento, lo ideal es que cada vez que hiciera un push a un sitio se sincronizara también el otro de forma automática. La solución pasa por emplear los <em>paths</em> para definir alias para los repositorios remotos y un <em>hook</em> para automatizar la sincronización. </p> <p>Definir los alias con <em>paths</em> es realmente sencillo, nos vamos al fichero <code>.hg/hgrc</code> del repositorio local y añadimos esto (e.g. el repo de este blog):</p> <div class="codehilite"><pre><span class="k">[paths]</span> <span class="na">github</span> <span class="o">=</span> <span class="s">git+ssh://git@github.com:joedicastro/joedicastro.com.git</span> <span class="na">bitbucket</span> <span class="o">=</span> <span class="s">ssh://hg@bitbucket.org/joedicastro/joedicastro.com</span> </pre></div> <p>De este modo, realizar un <code>push</code> es tan sencillo como:</p> <div class="codehilite"><pre><span class="gp">$</span> hg push bitbucket </pre></div> <p>Ahora necesitamos crear el <code>hook</code> que nos sincronice los dos alojamientos. Hay en la red varias soluciones para esto, por ejemplo <a href="http://morgangoose.com/blog/2010/09/29/github-and-bitbucket-hooks/">esta</a> y <a href="http://wiki.ddenis.com/index.php?title=Sync_BitBucket_and_GitHub">esta</a>, pero ninguna de las dos acababa de convencerme, la una por emplear un script bash que entraba muy fácilmente en un bucle infinito y la otra por necesitar otro modulo externo, que en mi caso no acababa de funcionar. Así que basándome en la idea del script bash del primero, decidí crearme uno en Python que funcionara correctamente y me solucionara el problema. </p> <p>El código del <code>hook</code> es el siguiente:</p> <div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span> <span class="c"># encoding: utf-8</span> <span class="sd">&quot;&quot;&quot;</span> <span class="sd"> bb_gh_sync.py: Mercurial hook to keep synced a repo to Bitbucket &amp; GitHub.</span> <span class="sd">&quot;&quot;&quot;</span> <span class="c">#==============================================================================</span> <span class="c"># This script maintain synced a repository to booth github and bitbucket sites,</span> <span class="c"># using only a local mercurial repository. To do this, makes use of hg-git, the</span> <span class="c"># paths defined in my local hg repo and the environment variable given by hg, to</span> <span class="c"># push to the site non described in the command line argument. This way, it&#39;s</span> <span class="c"># irrelevant which site I decided to push every time, booth are done by this</span> <span class="c"># hook.</span> <span class="c">#===============================================================================</span> <span class="c">#==============================================================================</span> <span class="c"># Copyright 2012 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;23/04/2012&quot;</span> <span class="n">__version__</span> <span class="o">=</span> <span class="s">&quot;0.1&quot;</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">from</span> <span class="nn">tempfile</span> <span class="kn">import</span> <span class="n">gettempdir</span> <span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">call</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">tmp_dir</span> <span class="o">=</span> <span class="n">gettempdir</span><span class="p">()</span> <span class="n">lock_file</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">join</span><span class="p">(</span><span class="n">tmp_dir</span><span class="p">,</span> <span class="s">&quot;bb_gh_sync.lock&quot;</span><span class="p">)</span> <span class="c"># make sure that only runs once for each repository</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">lock_file</span><span class="p">):</span> <span class="nb">open</span><span class="p">(</span><span class="n">lock_file</span><span class="p">,</span> <span class="s">&quot;w&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="c"># if pushed to bitbucket, push to github too</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">&#39;HG_ARGS&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s">&quot;push bitbucket&quot;</span><span class="p">:</span> <span class="n">call</span><span class="p">([</span><span class="s">&quot;/usr/bin/env&quot;</span><span class="p">,</span> <span class="s">&quot;hg&quot;</span><span class="p">,</span> <span class="s">&quot;push&quot;</span><span class="p">,</span> <span class="s">&quot;github&quot;</span><span class="p">])</span> <span class="c"># et viceversa...</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">&#39;HG_ARGS&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s">&quot;push github&quot;</span><span class="p">:</span> <span class="n">call</span><span class="p">([</span><span class="s">&quot;/usr/bin/env&quot;</span><span class="p">,</span> <span class="s">&quot;hg&quot;</span><span class="p">,</span> <span class="s">&quot;push&quot;</span><span class="p">,</span> <span class="s">&quot;bitbucket&quot;</span><span class="p">])</span> <span class="k">else</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">lock_file</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>Ahora solo tenemos que editar nuestro fichero <code>~/.hgrc</code> para habilitarlo y ya estaría listo para funcionar. Editamos el fichero y le añadimos estas lineas:</p> <div class="codehilite"><pre><span class="k">[hooks]</span> <span class="na">post-push</span> <span class="o">=</span> <span class="s">$HOME/dotfiles/hg/bb_gh_sync.py</span> </pre></div> <p>Ahora, si hacemos un push a Bitbucket, el hace automáticamente un push también a GitHub al acabar el primero, y viceversa. De este modo, hacer un push a ambos alojamientos es tan sencillo como:</p> <div class="codehilite"><pre><span class="gp">$</span> hg push bitbucket <span class="go">pushing to ssh://hg@bitbucket.org/joedicastro/joedicastro.com</span> <span class="go">running ssh hg@bitbucket.org &#39;hg -R joedicastro/joedicastro.com serve --stdio&#39;</span> <span class="go">searching for changes</span> <span class="go">no changes found</span> <span class="go">running hook post-push: $HOME/dotfiles/hg/bb_gh_sync.py</span> <span class="go">pushing to git+ssh://git@github.com:joedicastro/joedicastro.com.git</span> <span class="go">creating and sending data</span> <span class="go">[&quot;git-receive-pack &#39;joedicastro/joedicastro.com.git&#39;&quot;]</span> <span class="go"> github::refs/heads/master =&gt; GIT:198e8cc9</span> <span class="go">running hook post-push: $HOME/dotfiles/hg/bb_gh_sync.py</span> <span class="gp">$</span> </pre></div> <p>De este modo puedo mantener copias de los repositorios locales en ambos sitios de manera automática y sincronizada, sin preocuparme, ni hacer un trabajo extra. Eso si, conviene prescindir de los wikis y documentarlo todo a través de ficheros <code>README.md</code> en formato Markdown para facilitar la integración de los dos sitios. Lo que por otro lado también ayuda a mantenerlos actualizados de manera más sencilla.</p> <div class="footnote"> <hr /> <ol> <li id="fn:gt"> <p>Bueno, algunos rivales como <a href="http://gitorious.org/">Gitorius</a>, también soportan esta característica &#160;<a href="#fnref:gt" rev="footnote" title="Jump back to footnote 1 in the text">&#8617;</a></p> </li> </ol> </div>joe di castroThu, 26 Apr 2012 21:15:00 +0200http://joedicastro.com/sincronizar-bitbucket-y-github.htmlmercurialhgrepositoriobitbucketgithubpythonConvertir ficheros djvu a pdf en Linuxhttp://joedicastro.com/convertir-ficheros-djvu-a-pdf-en-linux.html<p>Tengo por costumbre almacenar mis documentos escaneados en el formato <a href="http://es.wikipedia.org/wiki/DjVu">djvu</a>, que fue expresamente creado para esa tarea y que otorga la mejor calidad posible en el menor espacio. Es el formato perfecto para documentos complejos sobre los que no se va a realizar un <a href="http://es.wikipedia.org/wiki/Reconocimiento_%C3%B3ptico_de_caracteres">OCR</a> (aunque también lo soporta). Además es un formato abierto, por lo que nos garantiza que podrá seguir empleándose en un futuro. Pero a veces necesito compartir estos ficheros con otros y para evitarme problemas suelo convertirlos a un formato más conocido y difundido como <a href="http://es.wikipedia.org/wiki/Pdf">PDF</a>.</p> <p>Para realizar esta conversión empleo desde hace años (la primera versión es del 2009) un sencillo script en python. Ahora que he necesitado una conversión masiva de documentos de un formato al otro, he modificado el script para hacer esto más sencillo y he decidido compartirlo con cualquiera que pueda necesitarlo. </p> <h2 id="los_requisitos_previos">Los requisitos previos</h2> <p>Está diseñado para funcionar en Linux y necesita de la instalación de dos pequeños programas que son los que realmente realizan la conversión. Estos dos programas son <code>ddjvu</code> y <code>tiff2pdf</code>. Además de tener instalado <strong>Python</strong> en una versión <em>2.7</em> o superior. Estos dos programas vienen en los repositorios de prácticamente todas las distribuciones importantes dentro de los paquetes <a href="http://djvu.sourceforge.net/">djvulibre</a> y <a href="http://libtiff.maptools.org">libtiff</a>.</p> <p>En el caso de no tenerlos instalados, la instalación de los mismos es muy sencilla, para distribuciones basadas en Debian/Ubuntu:</p> <div class="codehilite"><pre><span class="gp">$</span> apt-get install djvulibre-bin libtiff-tools </pre></div> <p><code>ddjvu</code> nos extrae las páginas que conforman el documento <em>.djvu</em> a un archivo intermedio en formato <em>.tiff</em> y <code>tiff2pdf</code> nos lo convierte en <em>.pdf</em>.</p> <h2 id="modo_de_empleo">Modo de empleo</h2> <p>Emplearlo es muy sencillo, como se puede ver en la ayuda del mismo:</p> <div class="codehilite"><pre><span class="gp">$</span> djvu2pdf -h <span class="go">usage: djvu2pdf [-h] [-d | -z] [-v] file [file ...]</span> <span class="go">Converts a djvu file into a pdf file</span> <span class="go">positional arguments:</span> <span class="go"> file The djvu file</span> <span class="go">optional arguments:</span> <span class="go"> -h, --help show this help message and exit</span> <span class="go"> -d no compression. Best quality but big files.</span> <span class="go"> -z zip compression. More quality, more size.</span> <span class="go"> -v, --version show program&#39;s version number and exit</span> </pre></div> <p>Básicamente llamandalo desde python y poniendo a continuación el nombre del fichero/s es lo único que necesitamos para ejecutarlo, por ejemplo:</p> <div class="codehilite"><pre><span class="gp">$</span> ls <span class="go">documento.djvu documento_2.djvu</span> <span class="gp">$</span> python djvu2pdf.py documento.djvu documento_2.djvu <span class="gp">$</span> ls <span class="go">documento.djvu documento.pdf documento_2.djvu documento_2.pdf</span> </pre></div> <p>Opcionalmente tenemos las opciones <code>-d</code> y <code>-z</code>, que nos sirven para especificar si queremos no emplear compresión en el <em>.pdf</em> (por defecto emplea compresión <a href="http://es.wikipedia.org/wiki/Jpeg">jpeg</a>) o emplear compresión <a href="http://es.wikipedia.org/wiki/Formato_de_compresi%C3%B3n_ZIP">zip</a>, respectivamente. Si no empleamos compresión, la calidad final será la mejor posible, pero los archivos serán muy grandes. En cambio, empleando <em>zip</em>, tenemos unos ficheros ligeramente mayores a cambio de una calidad muy buena. Aunque la compresión <em>zip</em> puede dar problemas con algunos visores y lectores de ebooks.</p> <h2 id="el_script_djvu2pdfpy">El script, djvu2pdf.py</h2> <p>El contenido del scipt es el que sigue. Este está disponible como el fichero <code>djvu2pdf.py</code> dentro del repositorio <em>Python Recipes</em> que está alojado en <a href="http://github.com/joedicastro/python-recipes">github</a> y actualizado siempre a la última versión.</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"> djvu2pdf.py: Converts a .djvu file into a .pdf file</span> <span class="sd">&quot;&quot;&quot;</span> <span class="c">#==============================================================================</span> <span class="c"># This Script does exactly as the description above says.</span> <span class="c">#==============================================================================</span> <span class="c">#==============================================================================</span> <span class="c"># Copyright 2011 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;03/12/2011&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">from</span> <span class="nn">argparse</span> <span class="kn">import</span> <span class="n">ArgumentParser</span> <span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">Popen</span><span class="p">,</span> <span class="n">PIPE</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">def</span> <span class="nf">check_execs</span><span class="p">(</span><span class="o">*</span><span class="n">progs</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Check if the programs are installed, if not exit and report.&quot;&quot;&quot;</span> <span class="k">for</span> <span class="n">prog</span> <span class="ow">in</span> <span class="n">progs</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="n">Popen</span><span class="p">([</span><span class="n">prog</span><span class="p">,</span> <span class="s">&#39;--help&#39;</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span> <span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span> <span class="n">msg</span> <span class="o">=</span> <span class="s">&#39;The {0} program is necessary to run the script&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">prog</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="n">msg</span><span class="p">)</span> <span class="k">return</span> <span class="k">def</span> <span class="nf">arguments</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Defines the command line arguments for the script.&quot;&quot;&quot;</span> <span class="n">main_desc</span> <span class="o">=</span> <span class="s">&quot;&quot;&quot;Converts a djvu file into a pdf file&quot;&quot;&quot;</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="n">main_desc</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;file&quot;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s">&quot;+&quot;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;The djvu file&quot;</span><span class="p">)</span> <span class="n">group</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_mutually_exclusive_group</span><span class="p">()</span> <span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;-d&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s">&quot;qlty&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">&quot;store_const&quot;</span><span class="p">,</span> <span class="n">const</span><span class="o">=</span><span class="s">&quot;-d&quot;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;no compression. Best quality but big files.&quot;</span><span class="p">)</span> <span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;-z&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s">&quot;qlty&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">&quot;store_const&quot;</span><span class="p">,</span> <span class="n">const</span><span class="o">=</span><span class="s">&quot;-z&quot;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;zip compression. More quality, more size.&quot;</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">&quot;-v&quot;</span><span class="p">,</span> <span class="s">&quot;--version&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">&quot;version&quot;</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s">&quot;</span><span class="si">%(prog)s</span><span class="s"> {0}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">__version__</span><span class="p">),</span> <span class="n">help</span><span class="o">=</span><span class="s">&quot;show program&#39;s version number and exit&quot;</span><span class="p">)</span> <span class="k">return</span> <span class="n">parser</span> <span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">fname</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Process the external commands and report the errors.&quot;&quot;&quot;</span> <span class="n">errors</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">errors</span><span class="p">:</span> <span class="k">print</span><span class="p">(</span><span class="s">&quot;{0}: {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">fname</span><span class="o">.</span><span class="n">upper</span><span class="p">(),</span> <span class="n">line</span><span class="o">.</span><span class="n">rstrip</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="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">args</span> <span class="o">=</span> <span class="n">arguments</span><span class="p">()</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span> <span class="n">djvu_files</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">file</span> <span class="k">for</span> <span class="n">djvu</span> <span class="ow">in</span> <span class="n">djvu_files</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">djvu</span><span class="p">):</span> <span class="k">print</span><span class="p">(</span><span class="s">&quot;ERROR: cannot open &#39;{0}&#39; (No such file)&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">djvu</span><span class="p">))</span> <span class="k">else</span><span class="p">:</span> <span class="n">djvu_filename</span> <span class="o">=</span> <span class="n">djvu</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">&quot;.djvu&quot;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="n">tiff</span> <span class="o">=</span> <span class="s">&#39;{0}.tif&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">djvu_filename</span><span class="p">)</span> <span class="n">pdf</span> <span class="o">=</span> <span class="s">&#39;{0}.pdf&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">djvu_filename</span><span class="p">)</span> <span class="n">process</span><span class="p">([</span><span class="s">&#39;ddjvu&#39;</span><span class="p">,</span> <span class="s">&#39;-format=tiff&#39;</span><span class="p">,</span> <span class="n">djvu</span><span class="p">,</span> <span class="n">tiff</span><span class="p">],</span> <span class="n">tiff</span><span class="p">)</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">tiff</span><span class="p">):</span> <span class="n">quality</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">qlty</span> <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">qlty</span> <span class="k">else</span> <span class="s">&quot;-j&quot;</span> <span class="n">process</span><span class="p">([</span><span class="s">&#39;tiff2pdf&#39;</span><span class="p">,</span> <span class="n">quality</span><span class="p">,</span> <span class="s">&#39;-o&#39;</span><span class="p">,</span> <span class="n">pdf</span><span class="p">,</span> <span class="n">tiff</span><span class="p">],</span> <span class="n">pdf</span><span class="p">)</span> <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">tiff</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">check_execs</span><span class="p">(</span><span class="s">&#39;ddjvu&#39;</span><span class="p">,</span> <span class="s">&#39;tiff2pdf&#39;</span><span class="p">)</span> <span class="n">main</span><span class="p">()</span> </pre></div>joe di castroSat, 03 Dec 2011 00:00:00 +0100http://joedicastro.com/convertir-ficheros-djvu-a-pdf-en-linux.htmlscriptpythondjvupdflinuxFabric & Rsync para realizar Backupshttp://joedicastro.com/fabric-rsync-para-realizar-backups.html<p>En el <a href="http://joedicastro.com/sincronizar-dos-directorios-con-fabric-y-rsync.html">anterior articulo</a> empleaba <a href="http://fabfile.org/">fabric</a> y <a href="http://es.wikipedia.org/wiki/Rsync">rsync</a> para sincronizar un directorio local y uno remoto en ambas direcciones. Además le añadía las funcionalidades de <a href="http://joedicastro.com/logger-informes-legibles-para-tus-scripts-python.html">logger</a> y <a href="http://joedicastro.com/notificaciones-de-escritorio-en-ubuntu-desde-python.html">notify</a> para proporcionar información sobre el proceso durante y después de su ejecución. Y comenzaba el articulo recordando a <a href="http://joedicastro.com/sincronizar-una-carpeta-local-y-una-remota-a-traves-de-ftp-lftp-mirror.html">lftp-mirror</a>, el script que había creado para realizar la sincronización a través de FTP. Pero <strong>lftp-mirror</strong> realiza algo más que la sincronización, pues también permite realizar el archivado del directorio local en ficheros comprimidos y lanzar varias tareas en una sola ejecución.</p> <p>Ahora he añadido esta funcionalidad al fichero <strong>fabric</strong> creado anteriormente. Así empleando este fichero podemos realizar el Backup periódico de varios servidores en una sola operación y de forma completamente automática (basta con programar su ejecución). Se sincronizan los dos directorios y se crea un archivo comprimido del directorio local por cada día de la semana. De este modo siempre tenemos una copia del estado del directorio remoto de los últimos siete días. Y al final del proceso en nuestro correo, un email con el informe del resultado por cada una de las tareas ejecutadas.</p> <p>En este fichero, <strong>rsync_fabric.py</strong>, disponemos de tres posibles tareas:</p> <div class="codehilite"><pre><span class="gp">$</span> fab -l <span class="go"> A Fabric file for sync two directories (remote ⇄ local) with rsync.</span> <span class="go">Available commands:</span> <span class="go"> backup Sync from remote to local &amp; archive the local directory.</span> <span class="go"> down Sync from remote to local.</span> <span class="go"> up Sync from local to remote.</span> </pre></div> <p>Con la primera realizamos el backup (sincronización + archivado) y con las siguientes solo la sincronización desde o hacia el servidor. Una de las ventajas de fabric es que nos permite concatenar tareas fácilmente desde la línea de comandos, así podemos lanzar varias sincronizaciones de forma simultanea. Para poder realizar esto, creo una configuración de sincronización por defecto y después creo una función para cada una las tareas adicionales que simplemente redefinen los valores de estas variables globales. Por ejemplo:</p> <div class="codehilite"><pre><span class="c"># Variables globales de sincronización predefenidas</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@example.com&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/my_directory&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/home/my_user/backups/my_directory&quot;</span> <span class="c"># Redefinimos estas variables para otra configuración de sincronización. Por </span> <span class="c"># supuesto, pueden tratarse de servidores distintos.</span> <span class="k">def</span> <span class="nf">_databases</span><span class="p">():</span> <span class="k">global</span> <span class="n">env</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@example.com&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/databases&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/home/my_user/backups/databases&quot;</span> </pre></div> <p>Veamos ejemplos de como podemos utilizar estas tareas:</p> <div class="codehilite"><pre><span class="gp">#</span> <span class="s2">&quot;Si queremos sincronizar el contenido local hacia el remoto, por ejemplo </span> <span class="gp">#</span><span class="s2"> para subir los ficheros al servidor por primera vez. Empleando los valores </span> <span class="gp">#</span><span class="s2"> por defecto. El modificador -w lo empleo para que no se detenga en los </span> <span class="gp">#</span><span class="s2"> errores, que de ocurrir, los veremos luego en el informe final.&quot;</span> <span class="gp">$</span> fab -w up <span class="go">[localhost] local: rsync -pthrvz --delete /home/my_user/backups/my_directory/ </span> <span class="go"> username@example.com:my_directory</span> <span class="go">Done.</span> <span class="gp">#</span> <span class="s2">&quot;Pero también podemos especificar una tarea distinta a la por defecto de </span> <span class="gp">#</span><span class="s2"> este modo. Sincronizando desde el servidor a nuestro directorio local las </span> <span class="gp">#</span><span class="s2"> bases de datos.&quot;</span> <span class="gp">$</span> fab -w down:databases <span class="go">[localhost] local: rsync -pthrvz --delete username@example.com:databases/ </span> <span class="go">/home/my_user/backups/databases</span> <span class="go">Done.</span> <span class="gp">#</span> <span class="s2">&quot;Y por supuesto, podemos realizar varias tareas a la vez.&quot;</span> <span class="gp">$</span> fab -w down backup:databases <span class="go">[localhost] local: rsync -pthrvz --delete username@example.com:my_directory/ </span> <span class="go">/home/my_user/backups/my_directory</span> <span class="go">[localhost] local: rsync -pthrvz --delete username@example.com:databases/ </span> <span class="go">/home/my_user/backups/databases</span> <span class="go">Done.</span> </pre></div> <p>No empleo contraseña alguna, ni en el fichero ni en la línea de comandos, podría hacerse perfectamente, pero prefiero emplear una clave <a href="http://es.wikipedia.org/wiki/RSA">RSA</a> <a href="http://es.wikipedia.org/wiki/Criptograf%C3%ADa_asim%C3%A9trica">pública</a> autorizada para las sesiones SSH en el servidor. Es bastante más seguro y cómodo. En los ejemplos no se ve la salida de <em>rsync</em>, pues es capturada (así como los erores) para ser mostrada a posteriori en los informes. </p> <p>Un ejemplo de informe sería el siguiente:</p> <div class="codehilite"><pre>START TIME ===================================================================== miércoles 13/07/11, 19:48:55 ================================================================================ SCRIPT ========================================================================= fab (ver. Unknown) Fabric Rsync http://joedicastro.com Syncing username@example.com:databases to /home/my_user/backups/databases ================================================================================ RSYNC OUTPUT ___________________________________________________________________ receiving file list ... done sent 20 bytes received 825 bytes 153.64 bytes/sec total size is 827.76M speedup is 979595.42 ROTATE COMPRESSED COPIES _______________________________________________________ Created file: /home/my_user/backups/databases_13jul2011_19:49_mié.tar.gz Deleted old file: databases_13jul2011_19:37_mié.tar.gz DISK SPACE USED ================================================================ 1.60 GiB ================================================================================ END TIME ======================================================================= miércoles 13/07/11, 19:50:02 ================================================================================ </pre></div> <p>Que como podemos ver, ha tardado poco más de un minuto en sincronizar 827.56 Megabytes y el total de espacio ocupado por el directorio y los siete archivos comprimidos es de 1.60 Gibibytes (1,72 Gigabytes). </p> <h2 id="ventajas">Ventajas</h2> <p>Las ventajas de sincronizarlo con <strong>rsync + ssh</strong> vs <strong>ftp</strong>, como ya comenté en el anterior articulo son enormes. Se ahorra muchísimo tiempo y ancho de banda, lo que ayuda a no saturar la red y no tener que planificar con tanto cuidado las ventanas de backup. Por ejemplo he realizado unas pruebas y para las mismas condiciones: <strong>mismo servidor, mismo directorio, mismo horario y condiciones de red; la sincronización remoto → local a través de FTP emplea entre 35 y 45 minutos y cuando lo hacemos a través de rsync emplea entre 2 y 4 minutos</strong>. Ahí es nada, estamos hablando de un proceso ~13 veces más rápido. </p> <h2 id="c+digo">Código</h2> <p>El código del fichero fabric es el siguiente:</p> <div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span> <span class="c"># -*- coding: utf8 -*-</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">glob</span> <span class="kn">import</span> <span class="nn">tarfile</span> <span class="kn">import</span> <span class="nn">time</span> <span class="kn">from</span> <span class="nn">get_size</span> <span class="kn">import</span> <span class="n">get_size</span> <span class="k">as</span> <span class="n">_get_size</span> <span class="kn">from</span> <span class="nn">get_size</span> <span class="kn">import</span> <span class="n">best_unit_size</span> <span class="k">as</span> <span class="n">_best_unit_size</span> <span class="kn">from</span> <span class="nn">logger</span> <span class="kn">import</span> <span class="n">Logger</span> <span class="k">as</span> <span class="n">_logger</span> <span class="kn">from</span> <span class="nn">notify</span> <span class="kn">import</span> <span class="n">notify</span> <span class="k">as</span> <span class="n">_notify</span> <span class="kn">from</span> <span class="nn">fabric.api</span> <span class="kn">import</span> <span class="n">env</span><span class="p">,</span> <span class="n">local</span> <span class="n">LOG</span> <span class="o">=</span> <span class="n">_logger</span><span class="p">()</span> <span class="c">#===============================================================================</span> <span class="c"># RSYNC HOSTS</span> <span class="c">#===============================================================================</span> <span class="c"># Your default host. No need any more if only wants a host.</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/your/local/path&quot;</span> <span class="c"># If wants to use various hosts, then define the previous variables like this, </span> <span class="c"># one function per host. </span> <span class="k">def</span> <span class="nf">_host_1</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Host variables for host_1.&quot;&quot;&quot;</span> <span class="k">global</span> <span class="n">env</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host_1&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path/in/host_1&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/your/local/path/for/host_1&quot;</span> <span class="k">def</span> <span class="nf">_host_2</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Host variables for host_2.&quot;&quot;&quot;</span> <span class="k">global</span> <span class="n">env</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host_2&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path/in/host_2&quot;</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="s">&quot;/your/local/path/for/host_2&quot;</span> <span class="c"># ...</span> <span class="c">#</span> <span class="c"># def _host_n():</span> <span class="c"># &quot;&quot;&quot;Host variables for host_n.&quot;&quot;&quot;</span> <span class="c"># global env</span> <span class="c"># env.host_string = &quot;username@host_n&quot;</span> <span class="c"># env.remote = &quot;/your/remote/path/in/host_n&quot;</span> <span class="c"># env.local = &quot;/your/local/path/for/host_n&quot;</span> <span class="c">#===============================================================================</span> <span class="c"># END RSYNC HOSTS</span> <span class="c">#===============================================================================</span> <span class="k">def</span> <span class="nf">_log_start</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Create the Start time info block for the log.&quot;&quot;&quot;</span> <span class="c"># Init the log for multiple hosts. Do not repeat the previous logs.</span> <span class="k">if</span> <span class="n">LOG</span><span class="o">.</span><span class="n">get</span><span class="p">():</span> <span class="n">LOG</span><span class="o">.</span><span class="n">__init__</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">&quot;Start time&quot;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_log_end</span><span class="p">(</span><span class="n">task</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Create the End time info block and send &amp; write the log.&quot;&quot;&quot;</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Ended&quot;</span> <span class="p">,</span> <span class="s">&quot;ok&quot;</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">&quot;End time&quot;</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="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="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">send</span><span class="p">(</span><span class="s">&quot;Fabric Rsync ({0})&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">task</span><span class="p">))</span> <span class="k">def</span> <span class="nf">_check_local</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Create local directory if no exists.&quot;&quot;&quot;</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">):</span> <span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_rsync</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">delete</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Process the _rsync command.&quot;&quot;&quot;</span> <span class="n">_log_start</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="s">&quot;Fabric Rsync</span><span class="se">\n</span><span class="s">http://joedicastro.com&quot;</span><span class="p">,</span> <span class="s">&quot;Syncing {0} to {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">))</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Start syncing {0} to {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">),</span> <span class="s">&quot;info&quot;</span><span class="p">)</span> <span class="n">out</span> <span class="o">=</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;rsync -pthrvz {2} {0}/ {1}&quot;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="s">&quot;--delete&quot;</span> <span class="k">if</span> <span class="n">delete</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="s">&quot;&quot;</span><span class="p">),</span> <span class="n">capture</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Finished synchronization&quot;</span><span class="p">,</span> <span class="s">&quot;ok&quot;</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">&quot;Rsync Output&quot;</span><span class="p">,</span> <span class="n">out</span><span class="p">)</span> <span class="k">if</span> <span class="n">out</span><span class="o">.</span><span class="n">failed</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">&quot;Rsync Errors&quot;</span><span class="p">,</span> <span class="n">out</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_compress</span><span class="p">(</span><span class="n">path</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Compress a local directory into a gz file.</span> <span class="sd"> Creates a file for each weekday, an removes the old files if exists&quot;&quot;&quot;</span> <span class="n">os</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">pardir</span><span class="p">))</span> <span class="n">dir2gz</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">path</span><span class="p">)</span> <span class="n">old_gzs</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">&#39;{0}*{1}.tar.gz&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">dir2gz</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&#39;</span><span class="p">)))</span> <span class="n">gz_name</span> <span class="o">=</span> <span class="s">&quot;{0}_{1}.tar.gz&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">dir2gz</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;</span><span class="si">%d</span><span class="s">%b%Y_%H:%M_%a&#39;</span><span class="p">))</span> <span class="n">gz_file</span> <span class="o">=</span> <span class="n">tarfile</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">gz_name</span><span class="p">,</span> <span class="s">&quot;w:gz&quot;</span><span class="p">)</span> <span class="n">gz_file</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">arcname</span><span class="o">=</span><span class="n">dir2gz</span><span class="p">)</span> <span class="n">gz_file</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="n">output</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="n">join</span><span class="p">([</span><span class="s">&#39;Created file:&#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">path</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">getcwd</span><span class="p">(),</span> <span class="n">gz_name</span><span class="p">)])</span> <span class="k">for</span> <span class="n">old_gz</span> <span class="ow">in</span> <span class="n">old_gzs</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">old_gz</span><span class="p">)</span> <span class="n">output</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="n">join</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="s">&#39;Deleted old file:&#39;</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="p">,</span> <span class="n">old_gz</span><span class="p">])</span> <span class="k">return</span> <span class="n">output</span> <span class="k">def</span> <span class="nf">_archive</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Archive the local directory in a gz file for each weekday.&quot;&quot;&quot;</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&#39;Rsync&#39;</span><span class="p">,</span> <span class="s">&#39;Compressing folder...&#39;</span><span class="p">,</span> <span class="s">&#39;info&#39;</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;Rotate compressed copies&#39;</span><span class="p">,</span> <span class="n">_compress</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">))</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Finished compression&quot;</span><span class="p">,</span> <span class="s">&quot;ok&quot;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">_get_diskspace</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Get the disk space used by the local directory and archives.&quot;&quot;&quot;</span> <span class="n">gz_size</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">([</span><span class="n">_get_size</span><span class="p">(</span><span class="n">gz</span><span class="p">)</span> <span class="k">for</span> <span class="n">gz</span> <span class="ow">in</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s">&#39;{0}*.gz&#39;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">))])</span> <span class="n">log_size</span> <span class="o">=</span> <span class="n">_get_size</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">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</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">else</span> <span class="mi">0</span> <span class="n">local_size</span> <span class="o">=</span> <span class="n">_get_size</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">)</span> <span class="n">size</span> <span class="o">=</span> <span class="n">_best_unit_size</span><span class="p">(</span><span class="n">local_size</span> <span class="o">+</span> <span class="n">gz_size</span> <span class="o">+</span> <span class="n">log_size</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;Disk space used&#39;</span><span class="p">,</span> <span class="s">&#39;{0:&gt;76.2f} {1}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="s">&#39;s&#39;</span><span class="p">],</span> <span class="n">size</span><span class="p">[</span><span class="s">&#39;u&#39;</span><span class="p">]))</span> <span class="k">def</span> <span class="nf">up</span><span class="p">(</span><span class="n">server</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from local to remote.&quot;&quot;&quot;</span> <span class="nb">globals</span><span class="p">()[</span><span class="s">&quot;_&quot;</span> <span class="o">+</span> <span class="n">server</span><span class="p">]()</span> <span class="k">if</span> <span class="n">server</span> <span class="k">else</span> <span class="bp">None</span> <span class="n">_rsync</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">,</span> <span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span><span class="p">]),</span> <span class="n">dlt</span><span class="p">)</span> <span class="n">_log_end</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="k">def</span> <span class="nf">down</span><span class="p">(</span><span class="n">server</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">,</span> <span class="n">archive</span><span class="o">=</span><span class="bp">False</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from remote to local.&quot;&quot;&quot;</span> <span class="nb">globals</span><span class="p">()[</span><span class="s">&quot;_&quot;</span> <span class="o">+</span> <span class="n">server</span><span class="p">]()</span> <span class="k">if</span> <span class="n">server</span> <span class="k">else</span> <span class="bp">None</span> <span class="n">_check_local</span><span class="p">()</span> <span class="n">_rsync</span><span class="p">(</span><span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">env</span><span class="o">.</span><span class="n">remote</span><span class="p">]),</span> <span class="n">env</span><span class="o">.</span><span class="n">local</span><span class="p">,</span> <span class="n">dlt</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">archive</span><span class="p">:</span> <span class="n">_log_end</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="k">def</span> <span class="nf">backup</span><span class="p">(</span><span class="n">server</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from remote to local &amp; archive the local directory.&quot;&quot;&quot;</span> <span class="n">down</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="n">archive</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">_archive</span><span class="p">()</span> <span class="n">_get_diskspace</span><span class="p">()</span> <span class="n">_log_end</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> </pre></div> <p>El fichero siempre actualizado puede ser encontrado en el repositorio <em>Python Recipes</em> que está alojado en <a href="http://github.com/joedicastro/python-recipes">github</a> con el nombre <code>rsync_fabfile.py</code> </p>joe di castroWed, 13 Jul 2011 20:21:00 +0200http://joedicastro.com/fabric-rsync-para-realizar-backups.htmlpythonfabricrsyncbackupsincronizarSincronizar dos directorios con Fabric y Rsynchttp://joedicastro.com/sincronizar-dos-directorios-con-fabric-y-rsync.html<p>Anteriormente habíamos visto como <a href="http://joedicastro.com/sincronizar-una-carpeta-local-y-una-remota-a-traves-de-ftp-lftp-mirror.html">sincronizar un directorio remoto y uno local empleando solamente FTP</a>. Ahora vamos a ver la forma de hacerlo empleando <a href="http://es.wikipedia.org/wiki/Ssh">ssh</a> y <a href="http://es.wikipedia.org/wiki/Rsync">rsync</a>. Para ello vamos a utilizar otra vez <strong>Python</strong> y una herramienta muy valiosa para cualquier <a href="http://es.wikipedia.org/wiki/Administrador_de_sistemas">sysadmin</a> que se precie como es <a href="http://fabfile.org/">fabric</a> (que descubrí gracias a Manuel Viera en <a href="http://python.majibu.org/preguntas/11/libreria-para-emplear-con-ssh">esta pregunta en majibu</a>). Evidentemente realizar la sincronización con rsync esta a años luz de hacerlo con FTP, la velocidad de sincronización, el tiempo empleado y la cantidad de datos a mover son mucho menores. FTP es algo que debería utilizarse únicamente cuando no disponemos de acceso via SSH.</p> <p>La gran ventaja de <strong>fabric</strong> es que nos permite ahorrarnos el tener que implementar el acceso SSH con <a href="http://www.lag.net/paramiko/">paramiko</a> y la entrada de opciones y argumentos con <em>argparse</em>. Gracias a esto los scripts necesarios son mucho más cortos y limpios y su utilización es bastante más sencilla. Fabric ya incorpora una función para emplear rsync, <code>rsync_project</code>, dentro de su modulo de proyectos contribuidos <code>fabric.contrib.project</code></p> <p>Una forma de implementar esta sincronización en ambas direcciones empleando esta función predefinida sería esta:</p> <div class="codehilite"><pre><span class="kn">from</span> <span class="nn">fabric.api</span> <span class="kn">import</span> <span class="n">env</span><span class="p">,</span> <span class="n">hosts</span><span class="p">,</span> <span class="n">local</span> <span class="kn">from</span> <span class="nn">fabric.contrib.project</span> <span class="kn">import</span> <span class="n">rsync_project</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host&quot;</span> <span class="n">REMOTE_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path&quot;</span> <span class="n">LOCAL_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/local/path&quot;</span> <span class="nd">@hosts</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">)</span> <span class="k">def</span> <span class="nf">rsync_up</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&quot;yes&quot;</span><span class="p">):</span> <span class="n">rsync_project</span><span class="p">(</span><span class="n">REMOTE_PATH</span><span class="p">,</span> <span class="n">LOCAL_PATH</span> <span class="o">+</span> <span class="s">&quot;/&quot;</span><span class="p">,</span> <span class="n">delete</span><span class="o">=</span> <span class="bp">True</span> <span class="k">if</span> <span class="n">dlt</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="bp">False</span><span class="p">)</span> <span class="k">def</span> <span class="nf">rsync_down</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&quot;yes&quot;</span><span class="p">):</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;rsync -pthrvz {0}:{1}/ {2} {3}&quot;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">,</span> <span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="s">&quot;--delete&quot;</span> <span class="k">if</span> <span class="n">dlt</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="s">&quot;&quot;</span><span class="p">))</span> </pre></div> <p>Y luego solo tendríamos que llamar a la función deseada:</p> <div class="codehilite"><pre><span class="gp">#</span> <span class="s2">&quot;Para sincronizar de remoto a local&quot;</span> <span class="gp">$</span> fab rsync_down </pre></div> <blockquote> <p>Hay que tener en cuenta un detalle con fabric. Cuando se le pasa un parámetro, este es siempre convertido a una cadena. Luego al pasarle <code>True</code> o <code>False</code> no se convierte en un valor booleano, sino una cadena <code>"True"</code>o <code>"False"</code>. De ahí que compruebe si el parámetro coincide con <code>"yes"</code> en vez de un valor booleano.</p> </blockquote> <p>El problema con la función rsync predefinida de fabric es que esta pensada únicamente para subir archivos a un servidor remoto, es decir, es una sincronización en una sola dirección, por eso implemento la sincronización en sentido contrario sin emplearla y empleando <code>local</code>. La autentificación de la sesión SSH puede realizarse especificando la contraseña dentro del propio fichero, pero va en contra del sentido común emplear un método tan inseguro como este. Lo lógico es emplear autorizaciones de sesiones SSH sin contraseña por medio de una <a href="http://es.wikipedia.org/wiki/Criptograf%C3%ADa_asim%C3%A9trica">clave pública</a>.</p> <p>Podríamos prescindir de la librería incorporada dentro de fabric y tendríamos algo como esto:</p> <div class="codehilite"><pre><span class="kn">from</span> <span class="nn">fabric.api</span> <span class="kn">import</span> <span class="n">env</span><span class="p">,</span> <span class="n">local</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host&quot;</span> <span class="n">REMOTE_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path&quot;</span> <span class="n">LOCAL_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/local/path&quot;</span> <span class="k">def</span> <span class="nf">_rsync</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">delete</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Process the _rsync command.&quot;&quot;&quot;</span> <span class="n">output</span> <span class="o">=</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;rsync -pthrvz {0}/ {1} {2}&quot;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="s">&quot;--delete&quot;</span> <span class="k">if</span> <span class="n">delete</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="s">&quot;&quot;</span><span class="p">))</span> <span class="k">def</span> <span class="nf">up</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from local to remote.&quot;&quot;&quot;</span> <span class="n">_rsync</span><span class="p">(</span><span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">]),</span> <span class="n">dlt</span><span class="p">)</span> <span class="k">def</span> <span class="nf">down</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from remote to local.&quot;&quot;&quot;</span> <span class="n">_rsync</span><span class="p">(</span><span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">]),</span> <span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="n">dlt</span><span class="p">)</span> </pre></div> <p>Pero... un momento, si estamos empleado un comando local, no empleamos <code>rsync_project</code> y empleamos una clave pública para el acceso SSH, entonces no estamos empleando <strong>paramiko</strong>, ¿de que nos sirve emplear fabric?. Bueno, en realidad <code>rsync_project</code> también emplea <code>local</code>, por lo que no emplea paramiko. Pero las ventajas vienen de que, por ejemplo, este mismo script se podría modificar fácilmente para ejecutar rsync en el servidor en vez de en nuestra maquina local, empleando <code>run</code> en vez de <code>local</code>. Además podemos emplear el mismo fichero para añadir varias tareas más a realizar en el servidor, aparte de la sincronización. Podríamos prescindir de fabric y hacer esto mismo con un script con un número similar de líneas, pero esto nos permite centralizar todas las tareas más comunes sobre ese servidor en un único fichero. Por ejemplo podríamos añadir una tarea para hacer un respaldo previo de una base de datos en el servidor, empleando un comando remoto en el servidor, luego hacer la sincronización separada de la BDD y el resto de ficheros y finalmente eliminar ese respaldo. Puede haber cientos de razones para preferir emplear fabric antes de un script independiente para la sincronización.</p> <h2 id="ejecuci+n_desatendida_de_la_sincronizaci+n">Ejecución desatendida de la sincronización</h2> <p>Si queremos programar esta tarea, no sería mala idea que nos avisara de cuando comienza a ejecutarse y del resultado de la misma. Para ello puedo emplear <a href="http://joedicastro.com/logger-informes-legibles-para-tus-scripts-python.html">Logger</a> y <a href="http://joedicastro.com/notificaciones-de-escritorio-en-ubuntu-desde-python.html">notify</a>, para implementar esta funcionalidad.</p> <div class="codehilite"><pre><span class="kn">from</span> <span class="nn">logger</span> <span class="kn">import</span> <span class="n">Logger</span> <span class="k">as</span> <span class="n">_logger</span> <span class="kn">from</span> <span class="nn">notify</span> <span class="kn">import</span> <span class="n">notify</span> <span class="k">as</span> <span class="n">_notify</span> <span class="kn">from</span> <span class="nn">fabric.api</span> <span class="kn">import</span> <span class="n">env</span><span class="p">,</span> <span class="n">local</span> <span class="n">env</span><span class="o">.</span><span class="n">host_string</span> <span class="o">=</span> <span class="s">&quot;username@host&quot;</span> <span class="n">REMOTE_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/remote/path&quot;</span> <span class="n">LOCAL_PATH</span> <span class="o">=</span> <span class="s">&quot;/your/local/path&quot;</span> <span class="k">def</span> <span class="nf">_rsync</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">delete</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Process the _rsync command.&quot;&quot;&quot;</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="s">&quot;Fabric Rsync</span><span class="se">\n</span><span class="s">http://joedicastro.com&quot;</span><span class="p">,</span> <span class="s">&quot;Syncing {0} to {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</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">&quot;Start time&quot;</span><span class="p">)</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Start syncing {0} to {1}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">),</span> <span class="s">&quot;info&quot;</span><span class="p">)</span> <span class="n">output</span> <span class="o">=</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;rsync -pthrvz {0}/ {1} {2}&quot;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="s">&quot;--delete&quot;</span> <span class="k">if</span> <span class="n">delete</span> <span class="o">==</span> <span class="s">&quot;yes&quot;</span> <span class="k">else</span> <span class="s">&quot;&quot;</span><span class="p">),</span> <span class="n">capture</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">_notify</span><span class="p">(</span><span class="s">&quot;Rsync&quot;</span><span class="p">,</span> <span class="s">&quot;Finished&quot;</span><span class="p">,</span> <span class="s">&quot;ok&quot;</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">&quot;Output&quot;</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span> <span class="k">if</span> <span class="n">output</span><span class="o">.</span><span class="n">failed</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">&quot;Error&quot;</span><span class="p">,</span> <span class="n">output</span><span class="o">.</span><span class="n">stderr</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">&quot;End time&quot;</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">&quot;Fabric Rsync&quot;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">up</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from local to remote.&quot;&quot;&quot;</span> <span class="n">_rsync</span><span class="p">(</span><span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">]),</span> <span class="n">dlt</span><span class="p">)</span> <span class="k">def</span> <span class="nf">down</span><span class="p">(</span><span class="n">dlt</span><span class="o">=</span><span class="s">&#39;yes&#39;</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Sync from remote to local.&quot;&quot;&quot;</span> <span class="n">_rsync</span><span class="p">(</span><span class="s">&quot;:&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">env</span><span class="o">.</span><span class="n">host_string</span><span class="p">,</span> <span class="n">REMOTE_PATH</span><span class="p">]),</span> <span class="n">LOCAL_PATH</span><span class="p">,</span> <span class="n">dlt</span><span class="p">)</span> </pre></div> <p>De esta forma, nos avisaría con una notificación en el escritorio de su inicio y fin, y al acabarse la sincronización, tendríamos un informe en nuestro correo parecido a este:</p> <div class="codehilite"><pre>SCRIPT ========================================================================= fab (ver. Unknown) Fabric Rsync Syncing username@host:/your/remote/path to /your/local/path ================================================================================ START TIME ===================================================================== miércoles 06/07/11, 21:50:48 ================================================================================ OUTPUT _________________________________________________________________________ receiving file list ... done ./ index.php sent 48 bytes received 200 bytes 45.09 bytes/sec total size is 99 speedup is 0.40 END TIME ======================================================================= miércoles 06/07/11, 21:50:54 ================================================================================ </pre></div> <p>Este fichero está disponible en el repositorio <em>Python Recipes</em> alojado en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>joe di castroWed, 06 Jul 2011 22:02:00 +0200http://joedicastro.com/sincronizar-dos-directorios-con-fabric-y-rsync.htmlpythonfabricrsyncsincronizarPelican - Repositoriohttp://joedicastro.com/pelican-repositorio.html<p>Como complemento a la <a href="http://joedicastro.com/tag/pelican.html">serie de artículos</a> que he publicado sobre <a href="http://docs.notmyidea.org/alexis/pelican/">Pelican</a>, el software que genera este blog, añado el repositorio, <a href="http://joedicastro.com/pelican-configuracion-y-personalizacion.html">como había prometido</a>, del contenido del mismo. El repositorio empleaba el sistema de control de versiones <a href="http://mercurial.selenic.com/">Mercurial</a> y estába alojado en <a href="https://bitbucket.org/">Bitbucket</a>.</p> <p>Las ventajas de disponer del contenido del blog en un repositorio son las de poder enmendar un error con suma facilidad y en muy poco tiempo, además de la de poder trabajar con distintas versiones del mismo (pruebas y producción). Además el repositorio en Bitbucket me proporcionaba una copia de seguridad adicional del sitio sin esfuerzo alguno. Y si alguien está interesado en crear su propio blog con Pelican y quiere saber como he realizado el mio, ahí tiene las claves. Salvo el propio Pelican (que no tendría mucho sentido) todo el material empleado para generarlo está en el. Y disponiendo del fichero <strong>fabric</strong>, se pueden descargar Pelican e instalar el entorno virtual en un minuto. </p> <p>Para automatizar todas las tareas, incluso las más comunes del repositorio, he añadido al fichero fabric <em>fabfile.py</em> dos tareas más:</p> <div class="codehilite"><pre><span class="k">def</span> <span class="nf">commit</span><span class="p">(</span><span class="n">message</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Make a commit to the local mercurial repository.&quot;&quot;&quot;</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;hg add&quot;</span><span class="p">)</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;hg commit -m &#39;{0}&#39;&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">message</span><span class="p">))</span> <span class="k">def</span> <span class="nf">push</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Make a push to the remote mercurial repository.&quot;&quot;&quot;</span> <span class="n">local</span><span class="p">(</span><span class="s">&quot;hg push ssh://hg@bitbucket.org/joedicastro/joedicastro.com&quot;</span><span class="p">)</span> </pre></div> <p>Con estas puedo hacer un <code>commit</code> y un <code>push</code> a Bitbucket en un solo paso, por ejemplo:</p> <div class="codehilite"><pre><span class="go">fab commit:&quot;Añadido articulo: Pelican - Repositorio&quot; push</span> </pre></div> <p>También he cambiado la página que generaba los archivos del blog, ya que no me gustaba el formato anterior: una fecha, un articulo. He pasado de esto:</p> <div class="codehilite"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s2">&quot;base.html&quot;</span> <span class="cp">%}</span><span class="x"></span> <span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span><span class="x"></span> <span class="x">&lt;section id=&quot;content&quot; class=&quot;body&quot;&gt;</span> <span class="x">&lt;h1&gt;Archivos de </span><span class="cp">{{</span> <span class="nv">SITENAME</span> <span class="cp">}}</span><span class="x">&lt;/h1&gt;</span> <span class="x">&lt;dl&gt;</span> <span class="cp">{%</span> <span class="k">for</span> <span class="nv">article</span> <span class="k">in</span> <span class="nv">dates</span> <span class="cp">%}</span><span class="x"></span> <span class="x"> &lt;dt&gt;</span><span class="cp">{{</span> <span class="nv">article.locale_date</span> <span class="cp">}}</span><span class="x">&lt;/dt&gt;</span> <span class="x"> &lt;dd&gt;&lt;a href=&#39;</span><span class="cp">{{</span> <span class="nv">article.url</span> <span class="cp">}}</span><span class="x">&#39;&gt;</span><span class="cp">{{</span> <span class="nv">article.title</span> <span class="cp">}}</span><span class="x">&lt;/a&gt;&lt;/dd&gt;</span> <span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span><span class="x"></span> <span class="x">&lt;/dl&gt;</span> <span class="x">&lt;/section&gt;</span> <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="x"></span> </pre></div> <p>a esto:</p> <div class="codehilite"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s2">&quot;base.html&quot;</span> <span class="cp">%}</span><span class="x"></span> <span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span><span class="x"></span> <span class="x">&lt;section id=&quot;content&quot; class=&quot;body&quot;&gt;</span> <span class="x">&lt;h1&gt;Archivos de </span><span class="cp">{{</span> <span class="nv">SITENAME</span> <span class="cp">}}</span><span class="x">&lt;/h1&gt;</span> <span class="cp">{%</span>- <span class="k">set</span> <span class="nv">years_month</span> <span class="o">=</span> <span class="o">{}</span> -<span class="cp">%}</span><span class="x"></span> <span class="cp">{%</span>- <span class="k">set</span> <span class="nv">months</span> <span class="o">=</span> <span class="o">{</span><span class="m">1</span><span class="s1">:&#39;Enero&#39;</span><span class="o">,</span> <span class="m">2</span><span class="s1">:&#39;Febrero&#39;</span><span class="o">,</span> <span class="m">3</span><span class="s1">:&#39;Marzo&#39;</span><span class="o">,</span> <span class="m">4</span><span class="s1">:&#39;Abril&#39;</span><span class="o">,</span> <span class="m">5</span><span class="s1">:&#39;Mayo&#39;</span><span class="o">,</span> <span class="m">6</span><span class="s1">:&#39;Junio&#39;</span><span class="o">,</span> <span class="m">7</span><span class="s1">:&#39;Julio&#39;</span><span class="o">,</span> <span class="m">8</span><span class="s1">:&#39;Agosto&#39;</span><span class="o">,</span> <span class="m">9</span><span class="s1">:&#39;Septiembre&#39;</span><span class="o">,</span> <span class="m">10</span><span class="s1">:&#39;Octubre&#39;</span><span class="o">,</span> <span class="m">11</span><span class="s1">:&#39;Noviembre&#39;</span><span class="o">,</span> <span class="m">12</span><span class="s1">:&#39;Diciembre&#39;</span><span class="o">}</span> -<span class="cp">%}</span><span class="x"> </span> <span class="cp">{%</span>- <span class="k">for</span> <span class="nv">article</span> <span class="k">in</span> <span class="nv">dates</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">if</span> <span class="nv">article.date.year</span> <span class="k">not</span> <span class="k">in</span> <span class="nv">years_month</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">do</span> <span class="nv">years_month.update</span><span class="o">({</span><span class="nv">article.date.year</span><span class="o">:[</span><span class="nv">article.date.month</span><span class="o">]})</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">else</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">if</span> <span class="nv">article.date.month</span> <span class="k">not</span> <span class="k">in</span> <span class="nv">years_month</span><span class="o">[</span><span class="nv">article.date.year</span><span class="o">]</span> -<span class="cp">%}</span><span class="x"> </span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">do</span> <span class="nv">years_month</span><span class="o">[</span><span class="nv">article.date.year</span><span class="o">]</span><span class="nv">.append</span><span class="o">(</span><span class="nv">article.date.month</span><span class="o">)</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">endif</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">endif</span> -<span class="cp">%}</span><span class="x"></span> <span class="cp">{%</span>- <span class="k">endfor</span> <span class="cp">%}</span><span class="x"></span> <span class="cp">{%</span> <span class="k">for</span> <span class="nv">year</span> <span class="k">in</span> <span class="nv">years_month</span><span class="o">|</span><span class="nf">sort</span><span class="o">(</span><span class="nv">reverse</span><span class="o">=</span><span class="kp">True</span><span class="o">)</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> &lt;h2 class=&quot;year&quot;&gt;</span><span class="cp">{{</span> <span class="nv">year</span> <span class="cp">}}</span><span class="x">&lt;/h2&gt;&lt;dl&gt; </span> <span class="x"> </span><span class="cp">{%</span> <span class="k">for</span> <span class="nv">month</span> <span class="k">in</span> <span class="nv">years_month</span><span class="o">[</span><span class="nv">year</span><span class="o">]</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> &lt;dt class=&quot;month&quot;&gt;</span><span class="cp">{{</span> <span class="nv">months</span><span class="o">[</span><span class="nv">month</span><span class="o">]</span> <span class="cp">}}</span><span class="x">&lt;/dt&gt;</span> <span class="x"> </span><span class="cp">{%</span> <span class="k">for</span> <span class="nv">article</span> <span class="k">in</span> <span class="nv">dates</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span> <span class="k">if</span> <span class="nv">article.date.year</span> <span class="o">==</span> <span class="nv">year</span> <span class="k">and</span> <span class="nv">article.date.month</span> <span class="o">==</span> <span class="nv">month</span> -<span class="cp">%}</span><span class="x"></span> <span class="x"> &lt;dd&gt;&lt;span class=&quot;day&quot;&gt;</span><span class="cp">{{</span> <span class="nv">article.locale_date.split</span><span class="o">()[</span><span class="m">1</span><span class="o">]</span> <span class="cp">}}</span><span class="x">&lt;/span&gt; &lt;a href=&#39;</span><span class="cp">{{</span> <span class="nv">article.url</span> <span class="cp">}}</span><span class="x">&#39;&gt;</span><span class="cp">{{</span> <span class="nv">article.title</span> <span class="cp">}}</span><span class="x">&lt;/a&gt;&lt;/dd&gt;</span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">endif</span> <span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span>- <span class="k">endfor</span> <span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span><span class="x">&lt;/dl&gt;</span> <span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span><span class="x"></span> <span class="x">&lt;/section&gt;</span> <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="x"></span> </pre></div> <p>Donde los artículos están archivados por año, mes y día, con un formato que personalmente me agrada bastante más.</p> <p>El repositorio de este blog se puede encontrar en <a href="http://github.com/joedicastro/joedicastro.com">github</a>.</p>joe di castroTue, 05 Jul 2011 23:02:00 +0200http://joedicastro.com/pelican-repositorio.htmlpelicanpythonmarkdownrestructuredtextblogmercurialhg