Skip to content
  • Beginner

Document generation

katachi can generate Markdown documentation from an architecture { } definition.

Configure it in build.gradle.kts.

architecture-test/build.gradle.kts
katachi {
// ⭐️ The fully qualified name of the property holding your `architecture { }`
architecture = "com.example.projectArchitecture"
}

Once that is set, running the katachiDocs Gradle task generates the documentation.

Terminal window
./gradlew katachiDocs

Take the following definition as an example.

architecture {
title = "myapp"
"domain".group {
title = "Domain layer"
summary = "App-specific behavior and the values it acts on"
"UseCase" {
title = "Use case"
summary = "A single app-specific behavior that happens on a screen"
example("GetItemListUseCase", "The list of items")
example("SignInUseCase", "Signing in")
layout { "core/domain" / mainSourceSet / kotlin / "com/example/core/domain/useCase" / "*UseCase".ktFile() }
"has only an invoke method".konsist { /* ... */ }
}
"RepositoryInterface" {
title = "Repository"
summary = "The boundary where data is fetched and saved"
layout { "core/domain" / mainSourceSet / kotlin / "com/example/core/domain/repository" / "*Repository".ktFile() }
}
}
}
  • Directorybuild/katachi/docs/
    • README.md
    • Directorydomain/
      • README.md
      • UseCase.md
      • RepositoryInterface.md

The samples in katachi’s GitHub repository keep actually generated documentation in sample/<name>/docs/. See them for concrete examples as well.

By default the documentation is generated under build/katachi/docs/.

The destination can be changed by setting katachi.processors.docs.outputDir in build.gradle.kts.

katachi {
architecture = "com.example.projectArchitecture"
processors {
docs {
outputDir = rootProject.layout.projectDirectory.dir("docs/architecture")
}
}
}

These are the files generated from an architecture { } definition.

  • Directory<output directory>/
    • README.md
    • <Role>.md
  • Directory<output directory>/
    • README.md
    • <Role>.md
    • Directory<group>/
      • README.md
      • <Role>.md
      • Directory<nested group>/
        • README.md
        • <Role>.md

Groups can nest, and a nested group is generated below the directory of its parent group.

File and directory names are decided by the identifier (UseCase in "UseCase" { }, domain in "domain".group { }), not by title. Changing title does not change the file name.

Properties used to generate the documentation

Section titled “Properties used to generate the documentation”

The documentation is generated from the properties you set on Roles and groups.

Property What it becomes in the generated documentation Pages it is used on
title The heading of the Role’s page. Defaults to the Role’s identifier <Role>.md, and the link text in the index and listings
summary The single line that goes in the listing table <Role>.md, the parent’s README.md
description The body of the Role’s page. The Markdown you write goes in as is, newlines included. katachi adds no structure of its own, so the headings inside are yours to choose <Role>.md
example(name, description) A concrete example. Call it once per example; they appear in the order you called them ## Examples in <Role>.md
documented Set to false to keep it out of the documentation. It only stops being documented – it still takes part in assert() and the other checks —

Role definition

"UseCase" {
title = "UseCase"
summary = "A single app-specific behavior that happens on a screen"
description = """
A UseCase is a class representing one app-specific behavior.
""".trimIndent()
example("GetItemListUseCase", "Fetches the list of items")
example("SignInUseCase", "Signs in")
layout {
"src/main/kotlin/com/example/useCase" /
"*UseCase".ktFile()
}
"has only an invoke method".konsist { /* ... */ }
}

Generated Markdown

UseCase.md
[Architecture](README.md) ← breadcrumb
# UseCase ← title
A single app-specific behavior that happens on a screen ← summary
A UseCase is a class representing one app-specific behavior. ← description
## Placement ← layout { }
| Path | When to use |
|---|---|
| `src/main/kotlin/com/example/useCase/*UseCase.kt` | |
## Constraints ← the name of konsist { }
- has only an invoke method
## Examples ← example()
- `GetItemListUseCase` ... Fetches the list of items
- `SignInUseCase` ... Signs in

A group takes the same four properties a Role does. Only example() is missing.

Property What it becomes in the generated documentation Pages it is used on
title The heading of the group’s README. Defaults to the group’s identifier README.md, <group>/README.md
summary The single line in the parent’s document map and in the group’s section README.md, <group>/README.md
description The body of the group’s README <group>/README.md
documented Set to false to keep the whole group out of the documentation. It still takes part in the checks —
Careful: documented = false takes the group’s contents out with it

A group with documented = false takes its Roles and nested groups out of the documentation along with it, whatever those Roles write for themselves.

"build".group {
documented = false
"VersionCatalog" { } // leaves the documentation together with the group
}

The value itself is not copied onto the Roles and groups inside (each keeps what it wrote). A processor of your own that reads this value should walk up Role.groupPath and look at the parent groups’ values too.

group definition

"domain".group {
title = "Domain layer"
summary = "App-specific behavior and the values it acts on"
description = """
The layer that depends on neither the screens nor the storage.
""".trimIndent()
"UseCase" {
title = "Use case"
summary = "A single app-specific behavior that happens on a screen"
layout { "src/main/kotlin/com/example/domain/useCase" / "*UseCase".ktFile() }
}
"RepositoryInterface" {
title = "Repository"
summary = "The boundary where data is fetched and saved"
layout { "src/main/kotlin/com/example/domain/repository" / "*Repository".ktFile() }
}
}

Generated Markdown

domain/README.md
[Architecture](../README.md) ← breadcrumb
# Domain layer ← title
App-specific behavior and the values it acts on ← summary
The layer that depends on neither the screens nor the storage. ← description
| Role | Summary | ← title and summary of the Roles inside
|---|---|
| [Use case](./UseCase.md) | A single app-specific behavior that happens on a screen |
| [Repository](./RepositoryInterface.md) | The boundary where data is fetched and saved |
## Placement in this group ← layout { } of the Roles inside
```
src/main/kotlin/com/example/domain/
useCase/*UseCase.kt Use case
repository/*Repository.kt Repository
```

The heading and description of the top page (the README.md right under the output directory) are set by writing title and description directly inside architecture { }.

Property What it becomes in the generated documentation Pages it is used on
title The heading of the top page README.md, the breadcrumb of every page
description The body right under the heading README.md

architecture definition

val projectArchitecture = architecture {
title = "myapp"
description = """
The file layout of myapp.
myapp is designed on top of **MVVM * Clean Architecture**.
""".trimIndent()
"domain".group {
title = "Domain layer"
summary = "App-specific behavior and the values it acts on"
"UseCase" { /* ... */ }
}
}

Generated Markdown

README.md
# myapp documentation
The file layout of myapp.
myapp is designed on top of **MVVM * Clean Architecture**.
## Document map
### [Domain layer](./domain/README.md)
App-specific behavior and the values it acts on
...

To add your own sections to the documentation on top of the ones provided by default, such as the title, define a section with documentSection().

Declare a section with documentSection("heading"), and create a property to write it through with by.

val TestPolicy = documentSection("Test policy")
var MetadataScope.testPolicy by TestPolicy
Nesting sections

Sections can be nested. Declare the child on its parent section, as in TestPolicy.documentSection("Unit tests"). The heading level follows from the depth. The top level is ##, and each level down adds one #. Even if the parent has no body, its heading still appears when a child has one.

Section definition

val TestPolicy = documentSection("Test policy")
var MetadataScope.testPolicy by TestPolicy
val UnitTest = TestPolicy.documentSection("Unit tests")
var MetadataScope.unitTest by UnitTest
val projectArchitecture = architecture {
"UseCase" {
title = "Use case"
summary = "A single app-specific behavior that happens on a screen"
example("SignInUseCase", "Signing in")
layout { "src/main/kotlin/com/example/useCase" / "*UseCase".ktFile() }
unitTest = "Replace the repository with a fake and check the return value."
}
}

Generated Markdown

UseCase.md
[Architecture](README.md)
# Use case
A single app-specific behavior that happens on a screen
## Placement
| Path | When to use |
|---|---|
| `src/main/kotlin/com/example/useCase/*UseCase.kt` | |
## Examples
- `SignInUseCase` ... Signing in
## Test policy ← no body, but shown because a child has one
### Unit tests ← one level down, so ###
Replace the repository with a fake and check the return value.

Custom sections come after the built-in sections (## Placement / ## Constraints / ## Examples), in the order they are written in the definition.

  • Documentation is created by setting properties such as title, summary, description, and example in the architecture definition.