Building Websites with Markdown and Maven

If you want to setup a new web site, today there are a lot of frameworks and content management systems in the market. One of the most common is WordPress which can not only be used as a blog software but also to build pretty web pages. But WordPress has some disadvantages in my eyes. It is build with a lot of PHP code, you need a database and at least you will be attacked by masses of hackers. If you only plan to setup a small web site you can do this with pure HTML. But this isn’t an ideal solution if you want to separate content form design – which is a best practice in these days. 

Markdown & Maven

The solution I found was simply the combination of Markdown with Maven. Markdown is a lightweight markup language with plain text formatting syntax that can be converted into HTML using different tools. Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor. Maven on the other hand is mainly a build tool for java development but it also provides a feature to create web sites for project documentation. The latest version of the Maven Site Plug-In supports also Markdown syntax so it provides a simple solution to use Markdown together with Maven to render Web Sites.

Setup a Web Site Project

To start building a web site with maven you just need to setup a new maven project including the maven-site-plugin. You project structure looks like this:

src/
 | site/
 |  | markdown/
 |  | - index.md
 |  | - examples.md
 |  | resources/
 |  |  | css/
 | site.vm
 | site.xml
 pom.xml

The pom.xml file in the root folder contains the maven configuration. See the following example:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.soika.ralph</groupId>
 <artifactId>my-project</artifactId>
 <packaging>pom</packaging>
 <name>my-project</name>
 <version>0.0.1</version>
 <description>My Project</description>
 <build>
 <plugins>
 <!-- web site -->
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-site-plugin</artifactId>
 <version>3.4</version>
 <configuration>
 <locales>en</locales>
 <templateDirectory>src/site</templateDirectory>
 <template>site.vm</template>
 </configuration>
 </plugin>
 </plugins>
 </build>
</project>

The site.xml file in the /site/ folder is used to describe the site and place a menu structure which is automatically parsed by maven:

<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="Maven">
 <body>
 <links>
 <item name="Github" href="https://github.com" />
 <item name="Download" href="https://github.com/s"/>
 </links>
 <menu>
 <item name="Home" href="index.html"/>
 <item name="Examples" href="examples.html"/>
 </menu>
 </body>
</project>

The most interesting part is the site.vm file, which is a velocity template. This template is used to render each mardown file. Maven provides a lot of macros to render the output. The following example can be used to start:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

#macro ( link $href $name )
 #if ( ( $href.toLowerCase().startsWith("http") || $href.toLowerCase().startsWith("https") ) )
 <a href="$href" class="externalLink">$name</a>
 #else
 <a href="$href">$name</a>
 #end
#end

#macro ( banner $banner $id )
 #if ( $banner )
 #if( $banner.href )
 <a href="$banner.href" id="$id">
 #else
 <span id="$id">
 #end
 #if( $banner.src )
 #set ( $src = $banner.src )
 #if ( ! ( $src.toLowerCase().startsWith("http") || $src.toLowerCase().startsWith("https") ) )
 #set ( $src = $PathTool.calculateLink( $src, $relativePath ) )
 #set ( $src = $src.replaceAll( "\\", "/" ) )
 #end
 #if ( $banner.alt )
 #set ( $alt = $banner.alt )
 #else
 #set ( $alt = "" )
 #end
 <img src="$src" alt="$alt" />
 #else
 $banner.name
 #end

 #if( $banner.href )
 </a>
 #else
 </span>
 #end
 #end
#end

#macro ( links $links )
 #set ( $counter = 0 )
 #foreach( $item in $links )
 #set ( $counter = $counter + 1 )
 #set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) )
 #set ( $currentItemHref = $currentItemHref.replaceAll( "\\", "/" ) )
 #if ( $project.name == $item.name || $item.href == "index.html" )
 <span class="selected"><img src="$relativePath/images/arrow.gif" style="padding-right: 5px;">$item.name</span>
 #else
 #link( $currentItemHref $item.name )
 #end
 #if ( $links.size() > $counter )
 &nbsp;
 #end
 #end
#end




#macro ( menuItem $item )
 #set ( $collapse = "none" )
 #set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) )
 #set ( $currentItemHref = $currentItemHref.replaceAll( "\\", "/" ) )

 
 <li class="$collapse">
 #if ( $alignedFileName == $currentItemHref )
 <span class="selected">$item.name</span>
 #else
 #link( $currentItemHref $item.name )
 #end
 #if ( $item && $item.items && $item.items.size() > 0 )
 <ul>
 #foreach( $subitem in $item.items )
 #menuItem( $subitem )
 #end
 </ul>
 #end
 </li>
#end

#macro ( mainMenu $menus )
 #foreach( $menu in $menus )
 #if ( $menu.name )
 <h5>$menu.name</h5>
 #end
 #if ( $menu.items && $menu.items.size() > 0 )
 <ul>
 #foreach( $item in $menu.items )
 #menuItem( $item )
 #end
 </ul>
 #end
 #end
#end


#macro ( content $content )

#if ( $content.startsWith("<!-- custom site -->") )
 $content
 #else
 <div id="container">
 <div class="content">
 $content
 </div>
 </div>
 #end
#end 

#macro ( copyright )
 #if ( $project )
 #set ( $currentYear = ${currentDate.year} + 1900 )

 #if ( ${project.inceptionYear} && ( ${project.inceptionYear} != ${currentYear.toString()} ) )
 ${project.inceptionYear}-${currentYear}
 #else
 ${currentYear}
 #end

 #if ( ${project.organization} && ${project.organization.name} )
 ${project.organization.name}
 #end
 #end
#end

#macro ( publishDate $position $publishDate $version )
 #if ( $publishDate && $publishDate.format )
 #set ( $format = $publishDate.format )
 #else
 #set ( $format = "yyyy-MM-dd" )
 #end

 $dateFormat.applyPattern( $format )

 #set ( $dateToday = $dateFormat.format( $currentDate ) )

 #if ( $publishDate && $publishDate.position )
 #set ( $datePosition = $publishDate.position )
 #else
 #set ( $datePosition = "left" )
 #end

 #if ( $version )
 #if ( $version.position )
 #set ( $versionPosition = $version.position )
 #else
 #set ( $versionPosition = "left" )
 #end
 #end


 #if ( $datePosition.equalsIgnoreCase( $position ) )
 #if ( ( $datePosition.equalsIgnoreCase( "right" ) ) || ( $datePosition.equalsIgnoreCase( "bottom" ) ) )
 $prefix $i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateToday
 #if ( $versionPosition.equalsIgnoreCase( $position ) )
 &nbsp;| $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}
 #end
 #elseif ( ( $datePosition.equalsIgnoreCase( "navigation-bottom" ) ) || ( $datePosition.equalsIgnoreCase( "navigation-top" ) ) )
 <div id="lastPublished">
 $i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateToday
 #if ( $versionPosition.equalsIgnoreCase( $position ) )
 &nbsp;| $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}
 #end
 </div>
 #elseif ( $datePosition.equalsIgnoreCase("left") )
 <div>
 $i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateToday
 #if ( $versionPosition.equalsIgnoreCase( $position ) )
 &nbsp;| $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}
 #end
 </div>
 #end
 #elseif ( $versionPosition.equalsIgnoreCase( $position ) )
 #if ( ( $versionPosition.equalsIgnoreCase( "right" ) ) || ( $versionPosition.equalsIgnoreCase( "bottom" ) ) )
 $prefix $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}
 #elseif ( ( $versionPosition.equalsIgnoreCase( "navigation-bottom" ) ) || ( $versionPosition.equalsIgnoreCase( "navigation-top" ) ) )
 <div id="lastPublished">
 $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}
 </div>
 #elseif ( $versionPosition.equalsIgnoreCase("left") )
 <div>
 $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}
 </div>
 #end
 
 #end
#end


<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title>$title</title>
 <style type="text/css" media="all">
 @import url("$relativePath/css/creatix-basic.css");
 @import url("$relativePath/css/creatix-layout.css");
 @import url("$relativePath/css/custom.css");
 </style>
 

 
 <link rel="stylesheet" href="$relativePath/css/print.css" type="text/css" media="print" />
 #foreach( $author in $authors )
 <meta name="author" content="$author" />
 #end
 <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
 <meta http-equiv="Content-Type" content="text/html; charset=${outputEncoding}" />
 <meta http-equiv="expires" content="43200">
 
 #if ( $decoration.body.head )
 #foreach( $item in $decoration.body.head.getChildren() )
 ## Workaround for DOXIA-150 due to a non-desired behaviour in p-u
 ## @see org.codehaus.plexus.util.xml.Xpp3Dom#toString()
 ## @see org.codehaus.plexus.util.xml.Xpp3Dom#toUnescapedString()
 #set ( $documentHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" )
 #set ( $documentHeader = $documentHeader.replaceAll( "\\", "" ) )
 #if ( $item.name == "script" )
 $StringUtils.replace( $item.toUnescapedString(), $documentHeader, "" )
 #else
 $StringUtils.replace( $item.toString(), $documentHeader, "" )
 #end
 #end
 #end
 </head>
 <body>
 
 <div id="header">
 <div id="imixs-servicenav">
 
 <div class="row">
 <div class="col-12" >
 <span class="pull-right">
 #links( $decoration.body.links )#publishDate( "right" $decoration.publishDate $decoration.version ) 
 </span> 
 </div>
 </div>
 
 <div class="row">
 
 <div class="col-12" >
 #mainMenu( $decoration.body.menus )
 </div>
 </div>
 </div>
 </div>
 <!-- END header -->
 
 <!-- Content -->
 
 #content($bodyContent)

 <!-- END Content -->
 
 <div id="footer">
 <div id="footer1">
 #publishDate( "left" $decoration.publishDate $decoration.version )
 </div>
 <div id="footer2">
 </div>
 </div>
 <!-- END footer -->
 </body>
</html>

This template automatically renders a menue navigation and print out the proejct title, version and the latest build date.

Build the web site

After you have setup the project structure you can build the web site with the followng maven command:

mvn clean site

The result web site will be created in your project /target/site/ folder.

Autodeployment

Maven provides also a mechanism to upload a site automatically to a web server. This can be configured in the pom.xm by adding a ‘distributionManagement’ section. If you use ssh connection add the ‘wagon-ssh’ dependency. See the following example

<build>
 <plugins>
 ...
   <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-site-plugin</artifactId>
      <version>3.4</version>
      <configuration>
        <locales>en</locales>
        <templateDirectory>src/site</templateDirectory>
        <template>site.vm</template>
      </configuration>
      <dependencies>
          <dependency><!-- add support for ssh/scp -->
           <groupId>org.apache.maven.wagon</groupId>
           <artifactId>wagon-ssh</artifactId>
           <version>1.0</version>
          </dependency>
      </dependencies>
    </plugin>
  </plugins>
</build>
<distributionManagement>
   <site>
     <id>my-web-host</id> 
     <url>scp://my-server.com/home/</url>
   </site>
</distributionManagement>
....

The authentication mechanism for the configured web server can be added into the settings.xml

...
<servers>
 <server>
 <id>my-web-host</id>
 <username>my-account</username>
 <!-- <password>pass</password> -->
 </server>
</servers>
...

Now you can execute the site:deploy goal from your project directory:

mvn site:deploy

Note: A site must be generated first before executing site:deploy.
If you want to generate the site and deploy it in one go, you can utilize the site-deploy phase of the site lifecycle. To do this, just execute:

mvn site-deploy

Markdown and HTML

One advantage of Markdown is that you can insert HTML right in the middle of your text. This is pretty useful when you need some features not provided by the Markdown syntax but which are easy to do with HTML. Using the Maven site plugin you need to take care about headlines like h2, h3, … because these sub-headlines are interpreted as sections and will result in additional div tags.

In Addition the vilocity template engine provides several ways to customize the output of a markdown file.

Example Web Site

In the open source project ‘Creatix-CSS‘ can can find a Maven based example web site. You can use this as an template for your own project.

An example of a web site build with markdown and maven can be seen here: www.benjs.org.

Find also additional information about the maven site plugin and markdown here.

Leave a Reply

Your email address will not be published. Required fields are marked *