Loading...

Friday, September 19, 2014

Gradle Goodness: Adding Dependencies Only for Packaging to War

My colleague, Tom Wetjens, wrote a blog post Package-only dependencies in Maven. He showed a Maven solution when we want to include dependencies in the WAR file, which are not used in any other scopes. In this blog post we will see how we solve this in Gradle.

Suppose we use the SLF4J Logging API in our project. We use the API as a compile dependency, because our code uses this API. But in our test runtime we want to use the SLF4J Simple implementation of this API. And in our WAR file we want to include the Logback implementation of the API. The Logback dependency is only needed to be included in the WAR file and shouldn't exist in any other dependency configuration.

We first add the War plugin to our project. The war task uses the runtime dependency configuration to determine which files are added to the WEB-INF/lib directory in our WAR file. We add a new dependency configuration warLib that extends the runtime configuration in our project.

apply plugin: 'war'

repositories.jcenter()

configurations {
    // Create new dependency configuration
    // for dependencies to be added in 
    // WAR file.
    warLib.extendsFrom runtime
}

dependencies {
    // API dependency for Slf4j.
    compile 'org.slf4j:slf4j-api:1.7.7'

    testCompile 'junit:junit:4.11'

    // Slf4j implementation used for tests.
    testRuntime 'org.slf4j:slf4j-simple:1.7.7'

    // Slf4j implementation to be packaged
    // in WAR file.
    warLib 'ch.qos.logback:logback-classic:1.1.2'
}

war {
    // Add warLib dependency configuration
    classpath configurations.warLib

    // We remove all duplicate files
    // with this assignment.
    // geFiles() method return a unique
    // set of File objects, removing
    // any duplicates from configurations
    // added by classpath() method.
    classpath = classpath.files
}

We can now run the build task and we get a WAR file with the following contents:

$ gradle build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:war
:assemble
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

Total time: 6.18 secs
$ jar tvf build/libs/package-only-dep-example.war
     0 Fri Sep 19 05:59:54 CEST 2014 META-INF/
    25 Fri Sep 19 05:59:54 CEST 2014 META-INF/MANIFEST.MF
     0 Fri Sep 19 05:59:54 CEST 2014 WEB-INF/
     0 Fri Sep 19 05:59:54 CEST 2014 WEB-INF/lib/
 29257 Thu Sep 18 14:36:24 CEST 2014 WEB-INF/lib/slf4j-api-1.7.7.jar
270750 Thu Sep 18 14:36:24 CEST 2014 WEB-INF/lib/logback-classic-1.1.2.jar
427729 Thu Sep 18 14:36:26 CEST 2014 WEB-INF/lib/logback-core-1.1.2.jar
   115 Wed Sep 03 09:24:40 CEST 2014 WEB-INF/web.xml

Also when we run the dependencies task we can see how the implementations of the SLF4J API relate to the dependency configurations:

$ gradle dependencies
:dependencies

------------------------------------------------------------
Root project
------------------------------------------------------------

archives - Configuration for archive artifacts.
No dependencies

compile - Compile classpath for source set 'main'.
\--- org.slf4j:slf4j-api:1.7.7

default - Configuration for default artifacts.
\--- org.slf4j:slf4j-api:1.7.7

providedCompile - Additional compile classpath for libraries that should not be part of the WAR archive.
No dependencies

providedRuntime - Additional runtime classpath for libraries that should not be part of the WAR archive.
No dependencies

runtime - Runtime classpath for source set 'main'.
\--- org.slf4j:slf4j-api:1.7.7

testCompile - Compile classpath for source set 'test'.
+--- org.slf4j:slf4j-api:1.7.7
\--- junit:junit:4.11
     \--- org.hamcrest:hamcrest-core:1.3

testRuntime - Runtime classpath for source set 'test'.
+--- org.slf4j:slf4j-api:1.7.7
+--- junit:junit:4.11
|    \--- org.hamcrest:hamcrest-core:1.3
\--- org.slf4j:slf4j-simple:1.7.7
     \--- org.slf4j:slf4j-api:1.7.7

warLib
+--- org.slf4j:slf4j-api:1.7.7
\--- ch.qos.logback:logback-classic:1.1.2
     +--- ch.qos.logback:logback-core:1.1.2
     \--- org.slf4j:slf4j-api:1.7.6 -> 1.7.7

(*) - dependencies omitted (listed previously)

BUILD SUCCESSFUL

Total time: 6.274 secs

Code written with Gradle 2.1.

Thursday, August 28, 2014

Awesome Asciidoc: Write Extensions Using Groovy (or Java)

We can write extension for Asciidoctor using the extension API in Groovy (or any other JVM language) if we run Asciidoctor on the Java platform using AsciidoctorJ. Extensions could also be written in Ruby of course, but in this post we see how to write a simple inline macro with Groovy.

The extension API has several extension points (Source):

  • Preprocessor: Processes the raw source lines before they are passed to the parser
  • Treeprocessor: Processes the Document (AST) once parsing is complete
  • Postprocessor: Processes the output after the Document has been rendered, before it's gets written to file
  • Block processor: Processes a block of content marked with a custom style (i.e., name) (equivalent to filters in AsciiDoc)
  • Block macro processor: Registers a custom block macro and process it (e.g., gist::12345[])
  • Inline macro processor: Registers a custom inline macro and process it (e.g., btn:[Save])
  • Include processor: Processes the include::[] macro

To write an extension in Groovy (or Java) we must write our implementation class for a specific extension point and we must register the class so AsciidoctorJ knows the class can be used. Registering the implementation is very simple, because it is using the Java Service Provider. This means we have to place a file in the META-INF/services directory on the classpath. The contents of the file is the class name of the implementation class.

Let's start with the Asciidoc markup and then write an implementation to process the inline macro twitter that is used:

= Groovy Inline Macro

Sample document to show extension for Asciidoctor written in Groovy.

// Here we use the twitter: macro.
// The implementation is done in Groovy.
With the twitter macro we can create links to the user's Twitter page like twitter:mrhaki[].

To implement an inline macro we create a new class and extend InlineMacroProcessor. We override the process method to return the value that needs to replace the inline macro in our Asciidoc markup.

// File: src/main/groovy/com/mrhaki/asciidoctor/extension/TwitterMacro.groovy
package com.mrhaki.asciidoctor.extension

import org.asciidoctor.extension.*
import org.asciidoctor.ast.*

import groovy.transform.CompileStatic

@CompileStatic
class TwitterMacro extends InlineMacroProcessor {

    TwitterMacro(final String name, final Map<String, Object> config) {
        super(name, config)
    }

    @Override
    protected Object process(final AbstractBlock parent, 
        final String twitterHandle, final Map<String, Object> attributes) {

        // Define options for an 'anchor' element.
        final Map options = [
            type: ':link',
            target: "http://www.twitter.com/${twitterHandle}".toString()
        ] as Map<String, Object>

        // Prepend twitterHandle with @ as text link.
        final Inline inlineTwitterLink = createInline(parent, 'anchor', "@${twitterHandle}", attributes, options)

        // Convert to String value.
        inlineTwitterLink.convert()
    }

}

We have the implementation class so now we can register the class with Asciidoctor. To register our custom extensions we need to implement the ExtensionRegistry interface. We implement the register method where we can couple our extension class to Asciidoctor.

// File: src/main/groovy/com/mrhaki/asciidoctor/extension/TwitterMacroExtension.groovy
package com.mrhaki.asciidoctor.extension

import org.asciidoctor.extension.spi.ExtensionRegistry
import org.asciidoctor.extension.JavaExtensionRegistry
import org.asciidoctor.Asciidoctor

import groovy.transform.CompileStatic

@CompileStatic
class TwitterMacroExtension implements ExtensionRegistry {

    @Override
    void register(final Asciidoctor asciidoctor) {
        final JavaExtensionRegistry javaExtensionRegistry = asciidoctor.javaExtensionRegistry()
        javaExtensionRegistry.inlineMacro 'twitter', TwitterMacro
    }

}

The class that registers our extension must be available via the Java Service Provider so it is automatically registered within the JVM used to run Asciidoctor. Therefore we need to create the file META-INF/services/org.asciidoctor.extension.spi.ExtensionRegistry with the following contents:

# File: src/main/resources/META-INF/services/org.asciidoctor.extension.spi.ExtensionRegistry
com.mrhaki.asciidoctor.extension.TwitterMacroExtension

We have taken all steps necessary to have our inline macro implementation. We must compile the Groovy classes and add those with the Java Service Provider file to the classpath. We can package the files in a JAR file and define a dependency on the JAR file in our project. If we use Gradle and the Gradle Asciidoctor plugin we can also add the source files to the buildSrc directory of our project. The files will be compiled and added to the classpath of the Gradle project.

With the following Gradle build file we can process Asciidoc markup and execute the twitter inline macro. We store the source files in the buildSrc directory.

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.0'
    }
}

apply plugin: 'org.asciidoctor.gradle.asciidoctor'

The build file in the buildSrc directory has a dependency on AsciidoctorJ. This module makes it possible to run Asciidoctor on the JVM.

// File: buildSrc/build.gradle.
apply plugin: 'groovy'

repositories {
    jcenter()
}

dependencies {
    compile 'org.asciidoctor:asciidoctorj:1.5.0'
}

Let's see part of the HTML that is generated if we transform the Asciidoc markup that is shown at the beginning of this blog post. The twitter inline macro is transformed into a link to the Twitter page of the user:

...
<div class="paragraph">
<p>With the twitter macro we can create links to the user’s Twitter page like <a href="http://www.twitter.com/mrhaki">@mrhaki</a>.</p>
</div>
...

Andres Almiray also wrote about writing extensions with Gradle.

Written with Asciidoctor 1.5.0 and Gradle 2.0.

Tuesday, August 26, 2014

Awesome Asciidoc: Conditional Directive to Check If Document is On GitHub

In a previous blog post we learned about the conditional directives in Asciidoctor. Dan Allen mentioned a conditional directive that we can use to see if the document is used on GitHub. The conditional directive is called env-github.

We have the following Asciidoc markup for a document stored on GitHub:

:blogpost: http://mrhaki.blogspot.com/2014/08/awesome-asciidoc-check-if-document-is.html

= Asciidoc on GitHub

Sample document for {blogpost}[Aweseome Asciidoc blog post].

ifdef::env-github[]
This line is only visible if the document is on GitHub.
GitHub is using Asciidoctor {asciidoctor-version}.
endif::env-github[]

ifndef::env-github[This line is visible if not rendered on GitHub.]

To see what is rendered we can view the document on GitHub.

Written with Asciidoctor 1.5.0.

Friday, August 15, 2014

Awesome Asciidoc: Changing the FontAwesome CSS Location

To use font icons from FontAwesome we set the document attribute icons with the value font. The default link to the CSS location is https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.1.0/css/font-awesome.min.css. We can change the location for the FontAwesome CSS with document attributes.

If we want to use a different CDN to serve the CSS we can set the document attribute iconfont-cdn and set the URI as a value:

:icons: font

// Set new URI for reference to FontAwesome CSS
:iconfont-cdn: //maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css

== Sample doc

To reference the FontAwesome CSS from a relative location from our generated HTML page we can first unset the attribute iconfont-remote and set the attribute iconfont-name:

:icons: font

// First unset attribute to remotely link FontAwesome CSS
:iconfont-remote!:

// Specify name of FontAwesome CSS.
:iconfont-name: fontawesome-4.1.0

// We can optionally set the directory where CSS is stored.
:stylesdir: css

== Sample doc

In the generated HTML source we see the following link element:

...
<link rel="stylesheet" href="css/fontawesome-4.1.0.css">
...

Written with Asciidoctor 1.5.0.

Awesome Asciidoc: Change URI Scheme for Assets

When we define the document attribute icons with the value font the FontAwesome fonts are loaded in the generated HTML page. In the head section of the HTML document a link element to the FontAwesome CSS on https://cdnjs.cloudflare.com/ajax/libs is added. Also when we use the highlight.js or Prettify source highlighter a link to the Javascript files on the cdnjs.cloudflare.com server is generated. We can change the value of the scheme from https to http by setting the attribute asset-uri-scheme to http. Or we can leave out the scheme so a scheme-less URI is generated for the links. A scheme-less URI provides the benefit that the same protocol of the origin HTML page is used to get the CSS or Javascript files from the cdnjs.cloudflare.com server. Remember this might provide a problem if the HTML page is opened locally.

In the next sample Asciidoc markup we change the scheme to http:

:asset-uri-scheme: http
:icons:font

== Asset URI Scheme

Sample document.

In the generated HTML we see the new scheme value:

<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.1.0/css/font-awesome.min.css">

Now we leave the value of the asset-uri-scheme attribute empty:

:asset-uri-scheme: 
:icons:font

== Asset URI Scheme

Sample document.

The generated HTML now contains a link to the FontAwesome CSS with a scheme-less URI:

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.1.0/css/font-awesome.min.css">

Written with Asciidoctor 1.5.0.


Gradle Goodness: Suppress Progress Logging

Gradle has some sophisticated progress logging on the console. For example we can see how much percentage of the building process is done. The percentage value is updated on the same console line. The following snippet is a sample of such output > Building 0% > :dependencies > Resolving dependencies ':compile'. The information is updated on the same line, which is really nice. But sometimes we might need to run Gradle builds on a system that doesn't support this mechanism on the console or terminal, possibly an continuous integration server. To disable the progress logging we can set the environment variable TERM to the value dumb.

Written with Gradle 2.0.


Wednesday, August 13, 2014

Awesome Asciidoc: Adding Line Numbers to Source Code Listings

When we write technical documentation with Asciidoctor we can easily include source code listings. When we use the coderay or pygments source code highlighter we can also include line numbers. We must add the attribute linenums to the listing block in our markup. This attribute is used by the source highlighters to create and format the line numbers. We can specify that the line numbers must be generated in table mode or inline mode. When the line numbers are in table mode we can select the source code without the line numbers and copy it to the clipboard. If we use inline mode the line numbers are selectable and are copied together with the selected source code to the clipboard. To specify which mode we want to use for the line numbers we use the document attribute coderay-linenums-mode or pygments-linenums-mode depending on the source highlighter we use. We can use the values table (default) or inline.

= Source code listing

Code listings look cool with Asciidoctor and {source-highlighter}.

[source,groovy,linenums]
----
// File: User.groovy
class User {
    String username
}
----

[source,asciidoc,linenums]
----
# Hello world

Asciidoc(tor) is aweseome!
----

Let's generate HTML output from this markup and use different values for the source-highlighter and ...-linenums-mode attributes. First we use the Coderay source highlighter in table mode:

If we use inline mode (:coderay-linenums-mode: inline), we can see the line numbers are selected as well when we select the source code:

The following screenshots use the Pygments source highlighter with first a table mode for the line numbers and then inline mode:

Written with Asciidoctor 1.5.0.

Awesome Asciidoc: Changing Highlight.js Theme

Asciidoctor is a great tool for writing technical documentation. If we have source code in the Asciidoc markup we can set the document attribute source-highlighter to pigments, coderay, prettify and highlightjs. When we use highlight.js we can also add an extra document attribute highlightjs-theme with the value of a highlight.js theme. If we do not specify the highlightjs-theme the default theme github is used.

We use the following Asciidoc markup to see how the HTML output is when we transform the markup using the HTML backend:

:source-highlighter: highlightjs

= Source code listing

Code listings look cool with Asciidoctor and highlight.js with {highlightjs-theme} theme.

[source,groovy]
----
// File: User.groovy
class User {
    String username
}
----

[source,sql]
----
CREATE TABLE USER (
    ID INT NOT NULL,
    USERNAME VARCHAR(40) NOT NULL
);
----

The following screenshots show the result of applying different themes:

For a complete list of all available themes checkout highlight.js on Github.

If we use the Prettify source code highlighter we must use the prettify-theme document attribute.

Written with Asciidoctor 1.5.0.

Tuesday, August 12, 2014

Awesome Asciidoc: Using Comments

We can add comments to our Asciidoc markup. The comments will not be added to generated output. We can add both single and multiline comments in the markup. Single line comments start with a double slash (//). Multiline comments are enclosed in a block of four forward slashes (////).

The following sample markup defines Asciidoc markup with comments:

= Asciidoctor comments

// Paragraph with some text.
Writing documentation is fun with Asciidoctor.

* Item A
* Item B

// Divide lists with a single line comment.
// Now we have two lists, otherwise it would
// be a single list with 4 items.

* Item 1
* Item 2

////
With four forward slashed we can
start a multiline comment.

And we close it with another
four forward slashes.
////

Asciidoc is really like _programming_ documentation.

When we generate HTML output we don't see any of the comments, not even in the HTML source:

Written with Asciidoctor 1.5.0.

Awesome Asciidoc: Links Without URI Scheme

Since Asciidoctor 1.5.0 we can use the document attribute hide-uri-scheme to turn URLs into links, where the link text is displayed without the URI scheme. This can save typing when we simply want to add a URL without any special description.

In the next Asciidoc syntax we first define URLs without the hide-uri-scheme attribute, followed by URLs after the attribute is set:

Reference to http://www.mrhaki.com is 
turned into link +
`+<a href="http://www.mrhaki.com">http://www.mrhaki.com</a>+`.

To loose URI scheme we could write the link
as http://www.mrhaki.com[www.mrhaki.com]. Or we 
can use the new attribute `hide-uri-scheme` which is
added to Asciidoctor 1.5.0.

:hide-uri-scheme:

After applying the `hide-uri-scheme` attribute
the URI scheme is removed from the text in links.
So a reference to http://www.mrhaki.com is
turned into the link +
`+<a href="http://www.mrhaki.com">www.mrhaki.com</a>+`

This also works for other URI schemes like `file`.
For example 
file:///Users/mrhaki/file.txt is translated to +
`+<a href="file:///Users/mrhaki/file.txt">/Users/mrhaki/file.txt</a>+`.

When we generate output using the HTML backend we see the following output:

Written with Asciidoctor 1.5.0.