AsciiDoc : Découvrir et maîtriser la syntaxe pour une documentation efficace

24 June 2025

Sommaire

Ce billet présente la démarche complète de conception et de tests (TDD) d’un plugin Gradle écrit en Kotlin DSL. Ce plugin automatise la génération de sites statiques avec JBake et le déploiement Git via JGit, le tout à partir d’un fichier YAML de configuration.

1. Objectif

Créer un plugin Gradle :

  • configurable en Kotlin DSL ;

  • capable de :

    • générer un site statique à partir de JBake ;

    • déployer sur un dépôt Git distant via JGit ;

  • intégrable dans un workflow CI/CD ;

  • testé selon une démarche TDD.

2. Configuration déclarative

Voici un exemple de configuration YAML à modéliser dans notre DSL :

bake:
  srcPath: "./site/jbake"
  destDirPath: "bake"
pushPage:
  from: "bake"
  to: "cvs"
  repo:
    name: "trainings"
    repository: "https://github.com/pages-content/trainings.git"
    credentials:
      username: "USERNAME"
      password: "SECRET_TOKEN"
  branch: "main"
  message: "cheroliv.com"

Cette configuration est représentée via une extension Gradle déclarable en Kotlin DSL.

3. Architecture du plugin

jbake asciidoctor class diagram

4. Définition de l’extension DSL

open class WorkspaceEngineExtension {
    var outputDir: String = "build/site"
    var template: String = "freemarker"
    var repoUrl: String = ""
    var branch: String = "main"
    var message: String = "Generated by workspace-engine"
}

5. Définition de la tâche personnalisée

abstract class GenerateSiteTask : DefaultTask() {
    lateinit var gitAdapter: GitAdapter

    @get:Input abstract val repoUrl: Property<String>
    @get:InputDirectory abstract val outputDir: DirectoryProperty
    @get:Input abstract val branch: Property<String>
    @get:Input abstract val message: Property<String>

    @TaskAction
    fun run() {
        val repo = gitAdapter.cloneRepository(repoUrl.get(), outputDir.get().asFile)
        gitAdapter.commitAndPush(repo, branch.get(), message.get())
    }
}

6. Interface GitAdapter

interface GitAdapter {
    fun cloneRepository(uri: String, directory: File): Repository
    fun commitAndPush(repo: Repository, branch: String, message: String)
}

7. Implémentation concrète avec JGit

class JGitAdapter : GitAdapter {
    override fun cloneRepository(uri: String, directory: File): Repository {
        return Git.cloneRepository()
            .setURI(uri)
            .setDirectory(directory)
            .call()
            .repository
    }

    override fun commitAndPush(repo: Repository, branch: String, message: String) {
        Git(repo).use {
            it.add().addFilepattern(".").call()
            it.commit().setMessage(message).call()
            it.push().call()
        }
    }
}

8. Configuration DSL Kotlin

workspaceEngine {
    outputDir = "build/out"
    template = "freemarker"
    repoUrl = "https://github.com/cheroliv/trainings.git"
    branch = "main"
    message = "deploy from plugin"
}

9. Tests unitaires avec Mockito

9.1. Dépendances

testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")

9.2. Test du comportement Git

class GenerateSiteTaskMockitoTest {

    @TempDir
    lateinit var tempDir: File

    @Test
    fun `task should call clone and commitAndPush`() {
        val project = ProjectBuilder.builder().build()
        val task = project.tasks.create("generateSite", GenerateSiteTask::class.java)

        val mockGitAdapter = mock<GitAdapter>()
        val mockRepo = mock<Repository>()

        whenever(mockGitAdapter.cloneRepository(any(), any())).thenReturn(mockRepo)

        task.gitAdapter = mockGitAdapter
        task.repoUrl.set("https://github.com/cheroliv/test.git")
        task.outputDir.set(project.layout.projectDirectory.dir(tempDir.name))
        task.branch.set("main")
        task.message.set("test commit")

        task.run()

        verify(mockGitAdapter).cloneRepository(eq("https://github.com/cheroliv/test.git"), any())
        verify(mockGitAdapter).commitAndPush(eq(mockRepo), eq("main"), eq("test commit"))
    }
}
jbake asciidoctor sequence diagram

10. Conclusion

La séparation des responsabilités via GitAdapter permet d’appliquer pleinement le TDD dans le développement de plugin Gradle :

  • les appels Git sont abstraits et testables ;

  • la configuration DSL est propre et claire ;

  • le plugin reste agnostique de l’implémentation réelle.

Cette approche assure une haute testabilité et extensibilité (vers GitHub API, GitLab, etc.).

11. Prochaines étapes

  • Intégrer la génération JBake comme un BakeAdapter

  • Ajouter des tests d’intégration avec GradleRunner

  • Supporter le chargement automatique de fichiers site.yml via SnakeYAML