News

Harnessing the Power of the Kotlin DSL for Documentation

Read this post in other languages:

This material was initially presented at the API The Docs Conference in Amsterdam, an event focused on developer success and how API documentation and developer portals help businesses. We highly recommend this conference to those who want to boost the technical aspect of their technical writer role. All talks are practical, informative, and include live demonstrations.

Sometimes, a few lines of code can help you complete a documentation task. However, docs aren’t code, and it can be harder to add external functionality or automations when writing documentation. If only you could write docs as if they were code, complete that particular task, and then turn the code back into docs!

In this blog post, we explore how to do just that using the Kotlin DSL to write documentation.

DSL? What DSL?

Let’s define “domain-specific language” or DSL.

Simply put, it’s a specialized programming language designed to solve specific problems. The concepts and rules of these languages are specific to a particular domain.

DSLs are also called little languages or problem-oriented languages. These definitions better describe their essence – DSLs are tailored to solve specific problems.

There are two types of DSLs. External DSLs are separate languages with their own grammar, lexer, and parser. SQL is an example of such a language. On the other hand, internal DSLs reuse the grammar and parser of a general-purpose language and make use of that language’s available extension options.

The Kotlin DSL for documentation falls into the category of internal DSLs.

With Kotlin, you can build an internal DSL with the look and feel of an external DSL. So, what we have developed is an internal DSL tailored specifically for documentation.

Read more about the differences between external and internal DSLs on the JetBrains MPS website.

The essence of the Kotlin DSL

The Kotlin DSL for documentation is a framework specifically designed for creating documentation and static websites.

The framework is a type-safe builder operating on basic document elements, while additional functionality plugs in as user code or external dependencies. The dependencies can deliver any kind of functionality – from specific source or output formats to natural language processing features.

With the Kotlin DSL, technical writers can define semantic elements, such as chapters, procedures, tables, lists, and more. Although it initially focuses on our own markup, the DSL’s flexibility allows for custom extensions, so in theory, we can turn any input format into any output format.

On the element level, users can encapsulate complex logic in custom elements, which will then be as simple to use as a paragraph or a list.

One of the significant advantages of the Kotlin DSL is that Integrated Development Environments (IDEs), like IntelliJ IDEA, provide built-in support, including error checking, code completion, and refactoring. This goes a long way in improving the quality of the written content.

An example of how duplicate detection works

As the sources are regular Kotlin code, all code intelligence and language support features are available out of the box. Need to navigate through the code? Want to detect dead and duplicate code? Or inject another language into a string literal? All these IDE features work for the Kotlin DSL. Speaking of the language itself, it offers significant benefits, such as full static typing and an entire ecosystem of useful tools.

While this blog post shows how the DSL generates our custom XML markup, it’s important to note that it can generate various output formats, such as HTML or .docx, based on your specific requirements.

Additionally, the DSL can be used not only in IntelliJ IDEA but also in other editors or even through the command line. It provides an experience that is still similar to writing content while also harnessing the endless possibilities offered by the Kotlin programming language.

Below are a few examples of what our markup may look like.

In the first example, we create a paragraph and a list with two list items and append some content inside these elements.

val content = StardustDslContentProvider {
   p { +"Hello, DSL"}
   list {
       li { +"List item 1" }
       li { +"List item 2" }
   }
   this
}

After running a script, it turns into the following result:

In the second example, we create a table with two columns.

val content = StardustDslContentProvider {
   table {
       tr {
           td {
               +"Parameter name"
           }
           td {
               +"Parameter type"
           }
       }
       tr {
           td {
               +"Value 1"
           }
           td {
               +"Value 2"
           }
       }
       this
   }

Which turns into this:

And in the third example, we demonstrate how to inject any other language, for example, XML or Markdown. In the case below, we are injecting a procedure with three steps:

xml("""
    <procedure title="Title">
        <step>Step 1</step>
        <step>Step 2</step>
        <step>Step 3</step>
    </procedure>
""")

Which results in this:

A procedure with 3 steps

But of course, just writing in markup is hardly a groundbreaking achievement. So, let’s proceed to something a little more innovative and exciting.

Demonstration

Firstly, please note that we want to show you an approach, not a particular implementation. The demonstration below is performed using our custom XML markup, but we can bridge any other format, like AsciiDoc, Markdown, reStructuredText, LaTeX to HTML, or even .docx.

Three key strengths of our approach

Separation of Concerns

We talk a lot about separating content from its presentation. This approach has existed in the web development world since the early days of CSS, and it was then reinterpreted and reinforced in lightweight markup languages. This technique involves decoupling the visual and design aspects of a document from its content and structure. This way, writers can focus on the content while easily adapting its presentation.

But we can go even further and separate data from its presentation. Instead of describing the world in markup, which has no direct relation to the things being described, the documentation may be generated based on the data itself. We can take data from the application runtime and then define its representation.

Moreover, we can make the representation conditional, dependent on the data design.
For example, if a method description indicates that it is deprecated, turn it into a warning element. In other cases, turn it into a paragraph.

It can look like this:

val descriptionTag = if (method.description.contains("DEPRECATED")) "warning" else "p"
xml(
   """
    <p>Method type: <b><font color="$font">${method.type.uppercase()}</font></b></p>
    <deflist>
        <def title="${method.summary}">
            <$descriptionTag>${method.description}</$descriptionTag>
        </def>
    </deflist>
   """
)

Docs-as-Code on a whole new level

We often speak about treating documentation as code, but we usually mean a limited amount of tools and practices, like writing in lightweight markup, putting docs under version control, and using code review and linters. But, there is so much more that we can take from the development world.

With the Kotlin DSL, documentation becomes synonymous with code, allowing writers to leverage practices commonly used by developers. Some of these practices include:

  • Making certain parts of content a module and then importing it as a dependency in many documentation sets, for example, some frontend components or legal information like copyright or license agreement.
  • Using function composition in programming, we can break down complex markup structures and patterns into simpler ones, making our sources more granular and easy to maintain and reuse.
  • Treating semantic and frontend components as types unlocks the advantages of object-oriented programming where we need them.

Extensibility

The extensibility of the DSL is a game-changer. Not only does it allow you to add custom elements, but it also offers the ability to modify the DSL itself, opening up endless possibilities.

You are only limited by your own imagination. Whatever is possible on a computer is possible with docs!

Thanks to seamless integration with existing JVM libraries, you can incorporate data sources, parse and enrich content, and even integrate with external tools and frameworks like Selenium and Cucumber.

A few examples of the extensibility options with the Kotlin DSL are:

  • Parsing and integrating any data format, including XML, JSON, YAML, custom serialization formats, and even objects from an application runtime.
  • Adding mappings and enriching docs with additional content.
  • Processing natural language with libraries such as Readability Analysis Tool and Apache OpenNLP.
  • Applying tests and assertions for autogenerated parts.

Why Kotlin?

Attempts to generate HTML from code have existed before, so you may be wondering why we‘ve selected Kotlin for documentation purposes?

First and foremost, Kotlin has advanced features that facilitate creating a powerful DSL based on it.

Second, Kotlin is renowned for its pragmatism and beginner-friendly nature. It embraces the principle of simplifying everyday tasks, ensuring that even newcomers can grasp the language quickly and easily.

For instance, writing a basic “Hello, World!” program in Kotlin doesn’t force beginners to operate with concepts and keywords they don’t understand.

Here’s an example that compares the “Hello, World!” program in Java with the same program in Kotlin:

Java:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Kotlin:

fun main() = println("Hello, World!")

In Kotlin, you don’t need to define a class or use the public static void signature. The main() function serves as the entry point, and you can directly use println() to output the desired message. This simplicity is one of the key advantages of Kotlin, making it easier and more enjoyable to write basic programs.

As mentioned before, Kotlin seamlessly integrates with the JVM ecosystem. With Java nearing its 30th anniversary, an abundance of immensely useful tools and libraries have been created. You can incorporate these tools into your workflow by simply adding a line to the build script.

By choosing Kotlin, we prioritize a language that strikes a balance between simplicity and powerful capabilities, ensuring an enjoyable and efficient developer experience.

Do I need to be a coder to use the Kotlin DSL?

We often hear writers say, “I am just an average technical writer, not a developer or super techie! How can I use this? What level of programming do I need in order to benefit from it?”

To start using the DSL elements as markup, you need zero coding skills, and you don’t need to know how DSL works under the hood.

Only when you have tasks related to some automations will you need to have a basic understanding of Kotlin. The Kotlin Core course at JetBrains Academy will be more than enough to teach you what you need to know.

However, we believe that an initial investment in learning the Kotlin DSL pays off in the long run. With its intuitive syntax and IDE support, writers can quickly become proficient and reap the benefits of a powerful documentation tool.

When you incorporate this approach into your documentation workflow, you may need to learn a bit of programming. By integrating the Kotlin DSL into your work, you can learn programming practically and engagingly, specifically tailored to your documentation responsibilities. After all, technical writers may find tasks related to documentation more enjoyable than synthetic exercises like calculating scores in a hypothetical class or building a basic calculator. Moreover, learning Kotlin helps with understanding other languages and ecosystems as well.

This approach may not resonate with all technical writers, but for those who write about developer tools, it can be a valuable way to enhance their skills.

Conclusion

The Kotlin DSL for documentation introduces a paradigm shift, enabling technical writers to create compelling content with the ease and power of a programming language. By leveraging Kotlin’s strengths, IDE support, and the inherent extensibility of the DSL, documentation becomes more than just words on a page. It becomes a dynamic, integrated part of the development process, enhancing collaboration and delivering exceptional results.

So, why not explore the possibilities of the Kotlin DSL for your own documentation needs? 

🚀 If you want to start using Kotlin DSL, we’ve created a sample project you may download and try to play with. If you have any questions regarding it, join our Slack, we have a dedicated channel called #kotlin-dsl-for-docs.

Embrace the power of code and take your documentation to new heights.

Happy writing and coding!

image description