Documentando Groovy con Groovydoc

Groovydoc se introdujo en 2007 para proporcionar a Groovy lo que Javadoc proporciona para Java. Groovydoc se usa para generar la documentación API para las clases Groovy y Java que componen el lenguaje Groovy. En esta publicación, miro cómo invocar Groovydoc a través de la línea de comandos y mediante la tarea Ant personalizada proporcionada por Groovy.

Código fuente Groovy y Java con comentarios Groovydoc / Javadoc

Usaré versiones adaptadas del script Groovy y las clases introducidas por primera vez en mi publicación de blog Easy Groovy Logger Injection y Log Guarding para demostrar Groovydoc. El script principal de Groovy y las clases de Groovy de esa publicación se han modificado para incluir más comentarios al estilo Javadoc para demostrar mejor Groovydoc en acción. El script revisado y las clases asociadas se muestran en los siguientes listados de código.

demoGroovyLogTransformation.groovy

#!/usr/bin/env groovy /** * demoGroovyLogTransformation.groovy * * Grab SLF4J, Log4j, and Apache Commons Logging dependencies using @Grab and * demonstrate Groovy 1.8's injected logging handles. * * //marxsoftware.blogspot.com/2011/05/easy-groovy-logger-injection-an... */ // No need to "grab" java.util.logging: it's part of the JDK! /* * Specifying 'slf4j-simple' rather than 'slf4j-api' to avoid the error * "Failed to load class "org.slf4j.impl.StaticLoggerBinder" that is caused by * specifying no or more than one of the actual logging binding libraries to * be used (see //www.slf4j.org/codes.html#StaticLoggerBinder). One should * be selected from 'slf4j-nop', 'slf4j-simple', 'slf4j-log4j12.jar', * 'slf4j-jdk14', or 'logback-classic'. An example of specifying the SLF4J * dependency via @Grab is available at * //mvnrepository.com/artifact/org.slf4j/slf4j-api/1.6.1. */ @Grab(group='org.slf4j', module="slf4j-simple", version="1.6.1") /* * An example of specifying the Log4j dependency via @Grab is at * //mvnrepository.com/artifact/log4j/log4j/1.2.16. */ @Grab(group='log4j', module="log4j", version="1.2.16") /* * An example of specifying the Apache Commons Logging dependency via @Grab is at * //mvnrepository.com/artifact/commons-logging/commons-logging-api/1..... */ @Grab(group='commons-logging', module="commons-logging-api", version="1.1") /* * Run the tests... */ int headerSize = 79 printHeader("java.util.logger", headerSize) def javaUtilLogger = new JavaUtilLoggerClass() printHeader("Log4j", headerSize) def log4jLogger = new Log4jLoggerClass() printHeader("SLF4j", headerSize) def slf4jLogger = new Slf4jLoggerClass() printHeader("Apache Commons", headerSize) def commonsLogger = new ApacheCommonsLoggerClass() /** * Print header with provided text. * * @param textForHeader Text to be included in the header. * @param sizeOfHeader Number of characters in each row of header. */ def printHeader(final String textForHeader, final int sizeOfHeader) { println "=".multiply(sizeOfHeader) println "= ${textForHeader}${' '.multiply(sizeOfHeader-textForHeader.size()-4)}=".multiply(sizeOfHeader) } 

JavaUtilLoggerClass.groovy

import groovy.util.logging.Log /** * Sample Groovy class using {@code @Log} to inject java.util.logging logger * into the class. */ @Log class JavaUtilLoggerClass { /** * Constructor. */ public JavaUtilLoggerClass() { println "\njava.util.logging (${log.name}: ${log.class}):" log.info "${this.printAndReturnValue(1)}" log.finer "${this.printAndReturnValue(2)}" } /** * Print provided value and then return it as part of String indicating part * of JDK's java.util.logging. * * @param newValue Value to be printed and included in return String. * @return String indicating newValue and JDK for java.util.logging. */ public String printAndReturnValue(int newValue) { println "JDK: Print method invoked for ${newValue}" return "JDK: ${newValue}" } } 

Log4jLoggerClass.groovy

import groovy.util.logging.Log4j import org.apache.log4j.Level /** * Sample Groovy class using {@code @Log4j} to inject Log4j logger * into the class. */ @Log4j class Log4jLoggerClass { /** * Constructor. */ Log4jLoggerClass() { // It is necessary to set logging level here because default is FATAL and // we are not using a Log4j external configuration file in this example log.setLevel(Level.INFO) println "\nLog4j Logging (${log.name}: ${log.class}):" log.info "${this.printAndReturnValue(1)}" log.debug "${this.printAndReturnValue(2)}" } /** * Print provided value and then return it as part of String indicating part * of Log4j. * * @param newValue Value to be printed and included in return String. * @return String indicating newValue and Log4j. */ public String printAndReturnValue(int newValue) { println "Log4j: Print method invoked for ${newValue}" return "Log4j: ${newValue}" } } 

Slf4jLoggerClass.groovy

import groovy.util.logging.Slf4j /** * Sample Groovy class using {@code @Slf4j} to inject Simple Logging Facade for * Java (SLF4J) logger into the class. */ @Slf4j class Slf4jLoggerClass { /** * Constructor. */ public Slf4jLoggerClass() { println "\nSLF4J Logging (${log.name}: ${log.class}):" log.info "${this.printAndReturnValue(1)}" log.debug "${this.printAndReturnValue(2)}" } /** * Print provided value and then return it as part of String indicating part * of SLF4J logging. * * @param newValue Value to be printed and included in return String. * @return String indicating newValue and SLF4J. */ public String printAndReturnValue(int newValue) { println "SLF4J: Print method invoked for ${newValue}" return "SLF4J: ${newValue}" } } 

ApacheCommonsLoggerClass.groovy

import groovy.util.logging.Commons /** * Sample Groovy class using {@code @Commons} to inject Apache Commons logger * into the class. */ @Commons class ApacheCommonsLoggerClass { /** * Constructor. */ public ApacheCommonsLoggerClass() { println "\nApache Commons Logging (${log.name}: ${log.class}):" log.info "${this.printAndReturnValue(1)}" log.debug "${this.printAndReturnValue(2)}" } /** * Print provided value and then return it as part of String indicating part * of Apache Commons Logging. * * @param newValue Value to be printed and included in return String. * @return String indicating newValue and Apache Commons Logging. */ public String printAndReturnValue(int newValue) { println "Commons: Print method invoked for ${newValue}" return "Commons: ${newValue}" } } 

Además del script y las clases de Groovy anteriores, también utilizo una nueva clase de Java aquí para ilustrar que Groovydoc funciona tanto en clases de Java como en clases de Groovy. La clase Java no hace mucho más que proporcionar los comentarios de Javadoc para que Groovydoc los procese.

DoNothingClass.java

/** * Class that does not do anything, but is here to be a Java class run through * groovydoc. */ public class DoNothingClass { /** * Simple method that returns literal "Hello _addressee_!" string where * _addressee_ is the name provided to this method. * * @param addressee Name for returned salutation to be addressed to. * @return "Hello!" */ public String sayHello(final String addressee) { return "Hello, " + addressee; } /** * Main executable function. */ public static void main(final String[] arguments) { final DoNothingClass me = new DoNothingClass(); me.sayHello("Dustin"); } /** * Provide String representation of this object. * * @return String representation of me. */ @Override public String toString() { return "Hello!"; } } 

Ejecutando Groovydoc en la línea de comandos

Con el script Groovy, las clases Groovy y la clase Java que se muestran arriba listas para usar, es hora de centrar la atención en ejecutar Groovydoc contra estas clases y script. Como es el caso de Javadoc, Groovydoc se puede ejecutar desde la línea de comandos. El comando para ejecutar Groovydoc contra las clases y scripts anteriores (suponiendo que estén todos en el mismo directorio en el que se ejecuta el comando) se parece a esto:

groovydoc -classpath C:\groovy-1.8.0\lib\ -d output -windowtitle "Groovy 1.8 Logging Example" -header "Groovy 1.8 Logging (Inspired by Actual Events)" -footer "Inspired by Actual Events: Logging in Groovy 1.8" -doctitle "Logging in Groovy 1.8 Demonstrated" *.groovy *.java 

El comando anterior se ejecuta en una sola línea. Sin embargo, para mejorar la legibilidad, agregué saltos de línea para dividir el comando.

groovydoc -classpath C:\groovy-1.8.0\lib\ -d output -windowtitle "Groovy 1.8 Logging Example" -header "Groovy 1.8 Logging (Inspired by Actual Events)" -footer "Inspired by Actual Events: Logging in Groovy 1.8" -doctitle "Logging in Groovy 1.8 Demonstrated" *.groovy *.java 

Los parámetros del comando groovydoc le resultan familiares a cualquiera que haya usado javadoc desde la línea de comando. La última parte del comando especifica que groovydoc debe ejecutarse con código Groovy y Java.

Ejecutando Groovydoc desde Ant

También se puede acceder fácilmente a Groovydoc a través de una tarea Ant personalizada como se describe en la Guía del usuario de Groovy. Es bastante fácil aplicar la tarea Groovydoc Ant configurando primero la definición de tarea adecuada y luego usando esa etiqueta definida. Esto se demuestra en el siguiente fragmento XML de un build.xmlarchivo relevante .

Partes de un archivo Ant build.xml que demuestran la tarea groovydoc


    
    

La parte de Ant que se build.xmlmuestra arriba es aproximadamente equivalente a la que se usa en la línea de comando. Tener Groovydoc disponible a través de Ant es importante porque facilita la integración de la construcción de la documentación de Groovy desde los sistemas de construcción basados ​​en Ant.

Documentación generada por Groovydoc

Debido a que cada enfoque para generar documentación Groovy a través de Groovydoc (línea de comandos o basado en Ant) funciona casi igual que el otro, ahora me enfocaré en la salida HTML que podría provenir de cualquiera de los enfoques. La siguiente serie de instantáneas de pantalla muestra la documentación generada comenzando con la página principal, seguida de la página DefaultPackage (dejé perezosamente el script, las clases Groovy y la clase Java en el directorio actual y sin ninguna declaración de paquete), seguido respectivamente por la salida para el script Groovy, para un ejemplo de la clase Groovy y para la clase Java artificial. Las últimas tres imágenes ayudan a diferenciar entre la salida de un Groovy Script frente a una clase Groovy frente a una clase Java.

Ejemplo de página principal de Groovydoc

Salida de Groovydoc para el paquete de ejemplo (DefaultPackage)

Salida de Groovydoc para ejemplo de secuencia de comandos Groovy

Salida de Groovydoc para la clase Groovy de ejemplo

Salida de Groovydoc para la clase Java de ejemplo

Se pueden hacer varias observaciones a partir de la salida de Groovydoc que se muestra arriba. Primero, la documentación generada para el script Groovy solo documentó los métodos definidos en el script (incluido el mainmétodo implícito ). Lo que no es tan obvio a partir de las imágenes estáticas anteriores es que, de hecho, no se crea ninguna salida de Groovydoc para un script a menos que al menos un método esté definido explícitamente en el script. Si se define un método en el script, entonces se genera la salida de Groovydoc para cualquier método definido y para el método principal implícito. La opción -nomainforscriptsse puede pasar a Groovydoc para que no se genere Groovydoc para el mainmétodo implícito . El resultado de agregar esta opción se muestra a continuación (tenga en cuenta que la maindocumentación de ya no se muestra).

La -nommainforscriptsopción es buena porque a menudo no queremos que la mainfunción esté documentada implícitamente para nuestros scripts. De hecho, la mainfunción normalmente está "oculta" a nosotros como escritores y mantenedores de guiones.

Una segunda observación de mirar la salida generada por Groovydoc es que la salida generada distingue entre Groovy y el código fuente de Java. Los scripts y clases Groovy están etiquetados con "[Groovy]" y las clases de Java están etiquetadas con "[Java]". Esto también es evidente en la documentación de la API Groovy generada por Groovydoc, donde estas características facilitan la identificación de que groovy.sql.Sql y AntBuilder son clases de Java, mientras que JmxBuilder y SwingBuilder son clases de Groovy.