Implementing cat with Kotlin Native

article cover image


Introduction​

Hello! đź‘‹

In this article we’ll implement the cat command utility which is used in many Unix like systems like Linux and macOS.To implement it, we’ll use kotlin and to drop the Java Virtual Machine (JVM). We’ll builda binary directly with Kotlin Native. In the end, we’ll get anexecutable that we can simply run on our Linux or macOS box.

What is cat?​

The catutility which has purpose of concatenating files. You can use it by typing cat file.txt and it will outputthe contents of the file into the terminal. It also has more options like: usage: cat [-belnstuv] [file ...] whichare documented in the manual pages.

Kotlin & Kotlin Native​

article kotlin image


Kotlin is a modern programming language that was first introduced by JetBrains in 2011 and later officiallysupported by Google for Android development. It’s designed to be fully interoperable with Java, while alsofixing some of the common pain points of Java, making it more concise, expressive, and enjoyable to use.

Kotlin Native is a technology for compiling Kotlin code to native binaries, which can run without a virtual machinelike the Java Virtual Machine (JVM). It is part of Kotlin Multiplatformwhich is technology designed to simplify cross-platform development. You can use Kotlin Multiplatform to develop softwarefor Server, Android, iOS, Desktop and Web - JavaScript.

Implementing cat with Kotlin Native​

To follow along, I recommend that you use the IntelliJ IDEA Communityor Professional integrated development environment (IDE).

Cloning the Kotlin Native template​

Start by cloning the Kotlin Native project template, I’ll come with everything preconfigured to get you started.

git clone https://github.com/Kotlin/kmp-native-wizard k-cat

Then open up the project in your IDE.

Modifying the template​

I’ve started modifying the template by first changing the project name to k-cat.

Open .../IdeaProjects/k-cat/settings.gradle.kts and set rootProject.name

rootProject.name = "k-cat"

And that’s pretty much everything we need so far to give our project a proper name! 🔩

Implementing cat​

The cat utility is a complex program. We’ll implement a simplified version that has the following requirements.

Requirements​

The program accepts a required string parameter which is implicit and represents the file pathand an optional -n flag parameter.

It will open the file given in the file path parameter, and it will print its contents on the terminal screen.

When called with the optional -n parameter it will print the line number before printing the file.

Example use:

Code:
./k-cat.kexe -n file.txt
0  Hello,
1  From nuculabs.dev

Implementation

To start implementing, please open: .../IdeaProjects/k-cat/src/nativeMain/kotlin/Main.kt.
Reading command line arguments

To read command line arguments, we need to add the args: Array<String> parameter to our main function. The args array will contain command line arguments passed to the program.

Code:
fun main(args: Array<String>) {
 //...
}

Parsing command line arguments​

To parse command line arguments, I’ve used simple Kotlin constructs.

The code is pretty much self-describing. First we search for the required filePath argument, and then we store itsvalue inside a variable. Secondly, we search for the optional -n shortform and --numbering longform argument, andif it’s found in the command line arguments array then we set the numbering boolean to true.

Code:
    // Grab the required file path argument
    val fileArgs = args.filterNot { it.startsWith("-") }
    if (fileArgs.size != 1) {
        println("Usage: missing path to file: ./k-cat <file>")
        return
    }
    val filePath = fileArgs.first()
    // Parse for optional `-n` argument
    var numbering = false;
    args.forEach {
        when (it) {
            "-n" -> numbering = true
            "--numbering" -> numbering = true
        }
    }

Reading Files​

Reading files can be done by using already available function or by using a library. I’ve chosen the later one andadded the okio dependency to my project.

To add a dependency open .../IdeaProjects/k-cat/build.gradle.kts and change sourceSets as following:

Code:
kotlin {
    //...
    sourceSets {
        val nativeMain by getting
        val nativeTest by getting
        val commonMain by getting {
            dependencies {
                implementation("com.squareup.okio:okio:3.7.0")
            }
        }
    }
}

Then you can import and use the library in your main function to read files. For example:

Code:
import okio.FileSystem
import okio.Path.Companion.toPath
import okio.use
import okio.buffer

FileSystem.SYSTEM.source("file.txt".toPath()).use { fileSource ->
    fileSource.buffer().use { bufferedFileSource ->
        while (true) {
          val line = bufferedFileSource.readUtf8Line() ?: break
          // do something with line
        }
    }
}

Implementing simple k-cat​

The implementation is now straightforward, since we know how to parse command line arguments and to read files.

The following piece of code implements k-cat. It reads all the lines from a file path, and if numbering is true, it willprint the line number and the line, otherwise it will only print the line.

Code:
    FileSystem.SYSTEM.source(filePath.toPath()).use { fileSource ->
        fileSource.buffer().use { bufferedFileSource ->
            var number = 0
            while (true) {
                val line = bufferedFileSource.readUtf8Line() ?: break
                if (numbering) {
                    val formattedNumber = number.toString().padEnd(2, ' ')
                    println("$formattedNumber $line")
                } else {
                    println(line)
                }
                number += 1
            }
        }
    }

Conclusion​

In summary, Kotlin Native extends the capabilities of Kotlin, enabling developers to compile Kotlin code into nativebinaries for a variety of platforms, including desktop, web, android, iOS and embedded systems.This extension offers the performance of native applications while maintaining Kotlin’s expressiveness and safety features.

Thanks for reading! Happy Holidays! 🎄✨

Further Reading / References​

Full Code: Main.kt​

Code:
import okio.FileSystem
import okio.Path.Companion.toPath
import okio.use
import okio.buffer

fun main(args: Array<String>) {
    // Grab the required file path argument
    val fileArgs = args.filterNot { it.startsWith("-") }
    if (fileArgs.size != 1) {
        println("Usage: missing path to file: ./k-cat <file>")
        return
    }
    val filePath = fileArgs.first()
    // Parse for optional `-n` argument
    var numbering = false;
    args.forEach {
        when (it) {
            "-n" -> numbering = true
            "--numbering" -> numbering = true
        }
    }

    FileSystem.SYSTEM.source(filePath.toPath()).use { fileSource ->
        fileSource.buffer().use { bufferedFileSource ->
            var number = 0
            while (true) {
                val line = bufferedFileSource.readUtf8Line() ?: break
                if (numbering) {
                    val formattedNumber = number.toString().padEnd(2, ' ')
                    println("$formattedNumber $line")
                } else {
                    println(line)
                }

                number += 1
            }
        }
    }
}
 
Back
Top