Building a cool CLI with Decline for my ZIO App

Pascal Mengelt
5 min readMay 5, 2020

Use Case

Working on a Project, there are always tasks you need to do frequently. Examples are:

  • starting Docker Images
  • deploying changes

I used to create some `bash` files, but that is not fun, neither to create them nor to use them.

So why not just write a CLI to do all these tasks? That’s what I did for my Hobby Project Camundala. This blog tells the story.

Spoiler: The story has got a happy ending😏.

The usual cute Cat replacement — for once it matches the library’s logo 😏

Requirements

I am sure by now, everyone has heard from ZIO, so I assume you know the basics. And everything is done in Scala.

What you should learn

  • What is Decline and how you use it.
  • Use Decline with a ZIO Console App.
  • Use Decline with Cats Effects.
  • Make your CLI fancy.
  • Create a ZLayer for your CLI.
  • Use your CLI in your ZIO App.

Decline

Composable command-line parsing for Scala

We can use Decline to parse and handle the console input. It provides a functional API and is based on Cats Effects — so it’s a perfect fit for my ZIO app.

Let’s start with Decline’s ‘get started’ example.

Basically you compose your Command Line App in a functional way — check out the building blocks. When you run this App with the --help flag you get this:

There are two things that has caught my interest:

  1. The help is already in place — Cool!
  2. Do I have to write Start Configurations to start the different options?

As the second point is suggesting, we need something that runs our CLI.

ZIO

Type-safe, composable asynchronous and concurrent programming for Scala

As the most of the ZIO demo projects are Console Apps — ZIO must be perfect for this!

This simple ZIO App will run our CLI and shows the same output as above and then it shuts down.

So how can we achieve our goal of having a Console App that runs forever? Except of addingforever of course😏.

I have added comments in the next code sample that explain the changes.

Cats Effects

For now we wrap the call to Decline with an Effect. But is Decline not based on Cats Effects — so there must be a better way. And of course there is, check also out the according documentation.

Decline offers in CommandIOApp the following effectful function that we can use:

def run[F[_]](command: Command[F[ExitCode]], args: List[String])(
implicit F: Sync[F]
): F[ExitCode]

As the signature suggests we are in Cats land — first lets create the Command.

The Opts do not change. The Command however gets now an Effect Type. As you can see it is not Cats, it’s our ZIO Task. What’s not possible, is to add an environment R or change the Result Type.

In our App you only have to change the call to Decline:

...
_ <- CommandIOApp.run(command, input.split(" ").toList)
...!

So we do not need to wrap the call anymore — Nice!

To make this work you need to add thezio-interop-catz module to your project.

Make it fancy

So we have now our CLI, but to be honest, it looks not very cool:

When we start there is no info at all. Even worse, as you can see above, if I do what the help tells me I got an exception. To fix that is easy, we just need to remove the name of the command. In reality we will have subcommands, so the help will make more sense.

All is left now is a cool intro — Yes ASCII-Art and Color are the keywords here. For the Art I used ManyTools.org. Here is the example of my Project:

Now we have a CLI✌!

We can add this to our App — just make sure you only show this ones. Here is my example with its color and ASCII art:

CLI as ZLayer

Ok back to business — we have a ZIO App. So the way to provide something in ZIO is via ZLayers. This looks like:

I do not go into details — if you are interested or have problem understanding it, check out my former Blog:

Our ZIO App still hasn’t changed much:

  • We call our Service via our access method (ZIO.accessM(_.get.run(input))).
_ <- helloWorld.run(input.split(" ").toList)
  • We provide our Layer to the App.
.provideCustomLayer(helloWorld.live)

Use the CLI in your App

We have seen already our CLI in action. The last chapter will show how we can use it as a part of our App. As example again my little Project.

The Project consists of three parts:

  1. Camunda, a BPMN Engine that is started as a Spring Boot App.
  2. A HTTP Server (http4s) to provide Services for the Camunda Modeler (deploy).
  3. Of course our CLI

All three apps run forever. So we need to run two of them in their own Fiber (.fork). If you are interested in the details, check out the TwitterApp example.

Here is the output when you run it:

And who has the coolest CLI?

And of course, the great help provided by Decline:

Conclusion

This was definitely worth the time — I can’t wait to add more functionality to my CLI.

ZIO and Decline fit really nicely together.

I hope you enjoyed my blog and learnt something. Feedback is always welcome!

References

I linked the important stuff in the text above. So here is just the link of the Project, that caused this Blog. Be aware the Project is in the beginning — so there will be changes.

--

--

Pascal Mengelt

Working for finnova.com in the Banking business. Prefer to work with Scala / ScalaJS.