#207 Syntax Highlighting
- Download:
- source codeProject Files in Zip (94.8 KB)
- mp4Full Size H.264 Video (19.9 MB)
- m4vSmaller H.264 Video (11.6 MB)
- webmFull Size VP8 Video (27.9 MB)
- ogvFull Size Theora Video (25 MB)
A veces necesitamos mostrar fragmentos de código en nuestra aplicación Rails, y para que el código sea más legible podemos utilizar el resaltado sintáctico. Es algo que podemos ver en todos los episodios de este sitio, pero ¿cómo hacerlo?
Disponemos de varias alternativas a la hora de añadir resaltado sintáctico en aplicaciones Rails. Lo primero que tendremos que decidir es si vamos a utilizar Rails para realizar el resaltado en el servidor o preferimos usar una librería JavaScript para transformar la página en el navegador. En este último caso existen varias librerías de resaltado sintáctico en JavaScript pero no las veremos aquí porque no son específicas de Rails. En su lugar veremos tres utilidades para realizar resaltado sintáctico que son populares en el mundo Rails.
CodeRay
En primer lugar tenemos CodeRay. Se instala fácilmente como gema, no tiene dependencias externas y es muy rápida aunque es un poco limitada en cuanto a funcionalidad y el número de lenguajes que soporta. Es una solución buena si cubre las necesidades de nuestra aplicación así que le echaremos un vistazo más adelante.
Ultraviolet
La siguiente opción es Ultraviolet. También es una gema pero tiene un par de dependencias externas que la hace un poco más difícil de instalar. Es más lenta que CodeRay pero da mucha más funcionalidad: soporta los ficheros de sintaxis de Textmate y por tanto es capaz de trabajar con más lenguajes que CodeRay. Tiene también más opciones de visualización.
Si escogemos utilizar Ultraviolet nos interesará Harsh, un plugin de Rails que proporciona un metodo helper para mostrar el código resaltado dentro de nuestras vistas, así como tareas Rake para instalar temas y gestionarlos. Se incluyen instrucciones en el README del proyecto.
Pygments
La tercera opción es Pygments. Se diferencia de CodeRay y Ultraviolet en que es una librería escrita en Python pero la podemos utilizar en nuestras aplicaciones Rails porque proporciona una utilidad de línea de comandos. Pygments es una librería completísima que se ampliamente utilizado, su desventaja es que puede llegar a ser bastante lenta cuando analiza documentos de gran tamaño por lo que en una aplicación Rails probablemente tendríamos que usar algún tipo de caché con Pygments.
Existe un plugin que hace que nos sea más fácil utilizar Pygments con Rails: highlight que nos permite invocar Pygments desde un método helper en las vistas. En las notas del plugin nos recomiendan cachear la salida que genera debido a los problemas anteriormente mencionados. Para ello lo único que tenemos que hacer es rodear dicha salida de un bloque cache
como este:
<h3>Prueba comparativa</h3> <p>Vamos a comparar estas tres opciones disponibles en términos de velocidad, puesto que puede darse el caso de que una apicación tenga que mostrar muchos fragmentos de código con resaltado sintáctico. Para comparar cada una de estas soluciones utilizaremos el siguiente código:</p> ``` ruby require 'rubygems' require 'benchmark' require 'coderay' require 'uv' path = __FILE__ content = File.read(__FILE__) # run it once to initialize CodeRay.scan("print 'hello'", 'ruby').div(:css => :class) Uv.parse("print 'test'", 'xhtml', 'ruby', true, 'amy') Benchmark.bm(11) do |b| b.report 'coderay' do 50.times { CodeRay.scan(content, 'ruby').div(:css => :class) } end b.report('ultraviolet') do 50.times { Uv.parse(content, 'xhtml', 'ruby', true, 'amy') } end b.report('pygments') do 50.times { `pygmentize -f html "#{path}"` } end end
El programa de prueba recibe el contenido de su propio código fuente y lo procesa cincuenta veces ocn cada librería (para Pygments utilizamos el comando pygmentize
). A continuación se muestran los resultados
<p>Los resultados ponen de manifiesto las importantes diferencias de rendimiento que tienen estas librerías. Obviamente CodeRay es la más rápida, empleando 0.3 segundos en las cincuenta iteraciones. Ultraviolet tardó diez veces más en realizar la misma tarea y Pygments fue aún más lenta, finalizando la prueba en doce segundos. Con tanta diferencia de velocidad es importante evaluar la funcionalidad y el rendimiento cuando tengamos que decidir qué librería utilizar (por ejemplo si vamos a utilizar caché la velocidad no será tan importante).</p> <h3>Uso de CodeRay</h3> <p>De aquí al final de este episodio veremos cómo utilizar CodeRay en una aplicación Rails. CodeRay es la más rápida y fácil de configurar de las tres librerías que estamos repasando por lo que no tendremos que preocuparnos de cachear sus resultados.</p> <p>Vamos a utilizar para nuestra demostración una aplicación sencilla de blog (que tal vez recuerden de episodios anteriores) incluyendo la capacidad de mostrar fragmentos de código con resaltado sintáctico dentro de los artículos.</p> <div class="imageWrapper"> <img src="http://railscasts.com/static/episodes/asciicasts/E207I01.png" width="817" height="464" alt="Nuestra aplicación de blog."/> </div> <p>Queremos que cuando un autor edite un artículo pueda añadir fragmentos de código entre etiquetas <code>code</code>:</p> ``` ruby <code lang="ruby"> puts "Hello, world!" </code>
Si aparece un fragmento como este en el cuerpo de un artículo debería verse con resaltado sintáctco.
En primer lugar tendremos que añadir una referencia a la gema de CodeRay en el fichero /config/environment.rb
(estamos trabajando con una aplicación Rails 2, con Rails 3 editaríamos en su lugar el archivo Gemfile
.
<p>Hemos añadido la gema RedCloth porque luego utilizaremos Textile. Existe una gema que combina estas dos librerías pero hemos preferido añadirlas por separado.</p> <p>A continuación actualizaremos la vista <code>show</code> de <code>ArticleController</code> para que analize con CodeRay el texto de las etiquetas <code>code</code> y realice el resaltado. Para ello, podemos rodear la línea de código que muestr el contenido del artículo en un nuevo <em>helper</em> llamado <code>coderay</code>:</p> ``` /app/views/articles/show.html.erb <% title @article.title %> <p class="author">from <%= @article.author %></p> <%= coderay(@article.content) %>
Este helper irá a nuestro archivo application_helper
:
(.+?)\<\/code\>/m) do
CodeRay.scan($3, $2).div(:css => :class)
end
end
end
<p>El método es bastante simple: recibe una cadena que puede contener secciones que nos interesen resaltar e invoca <code>gsub</code> sobre ella para encontrar dichas secciones y reemplazarlas por lo que se devuelva dentro del bloque. La expresión regular busca una etiqueta <code>code</code> con un atributo opcional <code>lang</code> y después asigna el texto entre las etiquetas de apertura y cierre. En el bloque se invoca el método <code>CodeRay.scan</code> que recibe un texto y un lenguaje como argumentos por lo que se le pasan <code>$3</code>, que se corresponderá con el texto entre las etiquetas y <code>$2</code> que será el contenido del atributo <code>lang</code>. Después llama al método <code>.div</code> para devolver la salida rodeada de un elemento <code>div</code>. La opción de <code>css => :class</code> le indica a CodeRay qué tipo de estilo CSS aplicar (CodeRay dispone de diferentes opciones para controlar la estructura y el aspecto de la salida, podemos encontrar más información en la <a href="http://coderay.rubychan.de/doc/">documentación</a>).</p>
<p>Por último tenemos que crear una hoja de estilos para definir los estilos de cada una de las clases de CodeRay. Ryan Bates ha creado una hoja de estilos similar a la que aparece en Railscasts que se puede <a href="http://github.com/ryanb/railscasts-episodes/blob/master/episode-207/blog/public/stylesheets/coderay.css">descargar de Github</a>. Vamos a poner una copia de este archivo en el directorio <code>/public/stylesheets</code>.</p>
``` /public/stylesheets/coderay.css
.CodeRay {
background-color: #232323;
border: 1px solid black;
font-family: 'Courier New', 'Terminal', monospace;
color: #E6E0DB;
padding: 3px 5px;
overflow: auto;
font-size: 12px;
margin: 12px 0;
}
.CodeRay pre {
margin: 0px;
padding: 0px;
}
.CodeRay .an { color:#E7BE69 } /* html attribute */
.CodeRay .c { color:#BC9358; font-style: italic; } /* comment */
.CodeRay .ch { color:#509E4F } /* escaped character */
.CodeRay .cl { color:#FFF } /* class */
.CodeRay .co { color:#FFF } /* constant */
.CodeRay .fl { color:#A4C260 } /* float */
.CodeRay .fu { color:#FFC56D } /* function */
.CodeRay .gv { color:#D0CFFE } /* global variable */
.CodeRay .i { color:#A4C260 } /* integer */
.CodeRay .il { background:#151515 } /* inline code */
.CodeRay .iv { color:#D0CFFE } /* instance variable */
.CodeRay .pp { color:#E7BE69 } /* doctype */
.CodeRay .r { color:#CB7832 } /* keyword */
.CodeRay .rx { color:#A4C260 } /* regex */
.CodeRay .s { color:#A4C260 } /* string */
.CodeRay .sy { color:#6C9CBD } /* symbol */
.CodeRay .ta { color:#E7BE69 } /* html tag */
.CodeRay .pc { color:#6C9CBD } /* boolean */
Hay una clase CSS para cada tipo de token encontrado en el código fuente original. Podemos cambiar fácilmente los colores en nuestra aplicación cambiando los valores de esta hoja de estilos.
Tenemos que añadir una referencia a esta hoja de estilos en la cabecera del fichero de layout de nuestra aplicación.
``` /app/views/layouts/application.html.erb
<%= stylesheet_link_tag 'blog', 'coderay' %>
<p>Con todo esto ya podemos editar un artículo y cuando lo visualicemos veremos el texto resaltado.</p>
<div class="imageWrapper">
<img src="http://railscasts.com/static/episodes/asciicasts/E207I02.png" width="815" height="420" alt="Ahora se resalta el fragmento de código."/>
</div>
<h3>Uso de Textile</h3>
<p>Es muy común combinar el resaltado sintáctico con un lenguaje sencillo de marcado como Textile o Markdown. Para utilizar Textile en nuestro artículo podemos rodear el método <code>coderay</code> de la vista <code>show</code> con una llamada a <code>textilize</code>.</p>
``` /app/views/articles/show.html.erb
<%= textilize(coderay(@article.content)) %>
Como no queremos que el código devuelto por el método coderay
sea modificado por RedCloth lo modificaremos para que devuelva la salida en un elemento notextile
de forma que sea pasado sin transformación.
``` /app/helpers/application_helper.rb
def coderay(text)
text.gsub(/\(.+?)\<\/code\>/m) do
content_tag("notextile", CodeRay.scan($3, $2).div(:css => :class))
end
end
<p>Si editamos otra vez el artículo y añadimos un marcado como este:</p>
``` terminal
The piano is a musical instrument played by means of a keyboard that produces sound by striking steel strings with felt hammers. The hammers immediately rebound allowing the strings to continue vibrating at their resonant frequency. These vibrations are transmitted through a bridge to a soundboard that amplifies them.
* item
* item 2
<code lang="ruby">
def hello
puts 'Hello, world!'
end
</code>
El artículo contendrá ahora una lista no ordenada generada por Textile así como el código con resaltado sintáctico.
Esto es todo por este episodio en el que hemos tratado del resaltado sintáctico. CodeRay hace que sea muy sencillo añadir esta funcionalidad a los fragmentos de código que tengamos que mostrar en nuestra aplicación.