Jody Winter Jody Winter Ralf D. Müller Schrotti Tim Riemer Alexander Schwartz Julian Elve Ralf D. Mueller

2 minutes to read

About This Task

This task crawls through your entire project looking for AsciiDoc files with a specific name pattern, then creates a single AsciiDoc file which includes only those files.

When you create modular documentation, most includes are static. For example, the arc42-template has 12 chapters and a master template that includes those 12 chapters.

Normally when you work with dynamic modules like ADRs (Architecture Decision Records) you create those files on the fly. Maybe not within your /src/docs folder, but alongside the code file for which you wrote the ADR. In order to include these files in your documentation, you have to add the file with its whole relative path to one of your AsciiDoc files.

This task will handle it for you!

Just stick to this file-naming pattern ^[A-Z]{3,}[-_].* (begin with at least three uppercase letters and a dash/underscore) and this task will collect the file and write it to your build folder. You only have to include this generated file from within your documentation. If you provide templates for the documents, those templates are skipped if the name matches the pattern ^.\*[-\_][tT]emplate[-\_].*.


You have a file called:


The task will collect this file and write another file called:


…​which will look like this:


Obviously, you’ll reap the most benefits if the task has several ADR files to collect. 😎

You can then include these files in your main documentation by using a single include:



import static*

task collectIncludes(
        description: 'collect all ADRs as includes in one file',
        group: 'docToolchain'
) {
    doFirst {
        new File(targetDir, '_includes').mkdirs()
    doLast {
        //let's search the whole project for files, not only the docs folder
        //could be a problem with node projects :-)

        //running as subproject? set scandir to main project
        if (! && scanDir=='.') {
            scanDir = project(':').projectDir.path
        if (docDir.startsWith('.')) {
            docDir = file(new File(projectDir, docDir).canonicalPath)
        } "docToolchain> docDir: ${docDir}" "docToolchain> scanDir: ${scanDir}"
        if (scanDir.startsWith('.')) {
            scanDir = file(new File(docDir, scanDir).canonicalPath)
        } else {
            scanDir = file(new File(scanDir, "").canonicalPath)
        } "docToolchain> scanDir: ${scanDir}" "docToolchain> includeRoot: ${includeRoot}"

        if (includeRoot.startsWith('.')) {
            includeRoot = file(new File(docDir, includeRoot).canonicalPath)
        } "docToolchain> includeRoot: ${includeRoot}"

        File sourceFolder = scanDir
        println "sourceFolder: " + sourceFolder.canonicalPath
        def collections = [:]
        sourceFolder.traverse(type: FILES) { file ->
            if ( ==~ '^[A-Z]{3,}[-_].*[.](ad|adoc|asciidoc)$') {
                def type ='^([A-Z]{3,})[-_].*$','\$1')
                if (!collections[type]) {
                    collections[type] = []
       "file: " + file.canonicalPath
                def fileName = (file.canonicalPath - scanDir.canonicalPath)[1..-1]
                if ( ==~ '^.*[Tt]emplate.*$') {
           "ignore template file: " + fileName
                } else {
                    if ( ==~ '^.*[A-Z]{3,}_includes.adoc$') {
               "ignore generated _includes files: " + fileName
                    } else {
                        if ( fileName.startsWith('docToolchain') || fileName.replace("\\", "/").matches('^.*/docToolchain/.*$')) {
                            //ignore docToolchain as submodule
                        } else {
                   "include corrected file: " + fileName
                            collections[type] << fileName
        println "targetFolder: " + (targetDir - docDir) "targetDir - includeRoot: " + (targetDir - includeRoot)
        def pathDiff = '../' * ((targetDir - docDir)
                                    .replaceAll("[^/]",'').size()+1) "pathDiff: " + pathDiff
        collections.each { type, fileNames ->
            if (fileNames) {
                def outFile = new File(targetDir+'/_includes', type + '_includes.adoc')
                outFile.write("// this is autogenerated\n")
                fileNames.sort().each { fileName ->
                    outFile.append ("include::../"+pathDiff+fileName.replace("\\", "/")+"[]\n\n")