Command Line Interface Application

    There are two main topics when building a CLI application:

    This topic covers all things related to:

    It is a very common practice to pass options to the application. For example, we may run crystal -v and Crystal will display:

    and if we run: crystal -h, then Crystal will show all the accepted options and how to use them.

    So now the question would be: do we need to implement an options parser? No need to, Crystal got us covered with the class OptionParser. Let’s build an application using this parser!

    At the start our CLI application has two options:

    • -v / --version: it will display the application version.
    • -h / --help: it will display the application help.

    ```crystal title=”help.cr” require “option_parser”

    OptionParser.parse do |parser| parser.banner = “Welcome to The Beatles App!”

    parser.on “-v”, “—version”, “Show version” do puts “version 1.0” exit end parser.on “-h”, “—help”, “Show help” do puts parser exit end end

    1. So, how does all this work? Well magic! No, its not really magic! Just Crystal making our life easy.
    2. When our application starts, the block passed to `OptionParser#parse` gets executed. In that block we define all the options. After the block is executed, the parser will start consuming the arguments passed to the application, trying to match each one with the options defined by us. If an option matches then the block passed to `parser#on` gets executed!
    3. We can read all about `OptionParser` in [the official API documentation](https://crystal-lang.org/api/OptionParser.html). And from there we are one click away from the source code ... the actual proof that it is not magic!
    4. Now, let's run our application. We have two ways [using the compiler](../using_the_compiler/README.md):
    5. 1. [Build the application](../using_the_compiler/README.md#crystal-build) and then run it.
    6. 2. Compile and [run the application](../using_the_compiler/README.md#crystal-run), all in one command.
    7. ```console
    8. $ crystal run ./help.cr -- -h
    9. Welcome to The Beatles App!
    10. -v, --version Show version
    11. -h, --help Show help

    Let’s build another fabulous application with the following feature:

    By default (i.e. no options given) the application will display the names of the Fab Four. But, if we pass the option -t / --twist it will display the names in uppercase:

    ```crystal title=”twist_and_shout.cr” require “option_parser”

    the_beatles = [ “John Lennon”, “Paul McCartney”, “George Harrison”, “Ringo Starr”, ] shout = false

    option_parser = OptionParser.parse do |parser| parser.banner = “Welcome to The Beatles App!”

    parser.on “-v”, “—version”, “Show version” do puts “version 1.0” exit end parser.on “-h”, “—help”, “Show help” do puts parser exit end parser.on “-t”, “—twist”, “Twist and SHOUT” do shout = true end end

    members = the_beatles members = the_beatles.map &.upcase if shout

    puts “” puts “Group members:” puts “==============” members.each do |member| puts member end

    1. Running the application with the `-t` option will output:
    2. $ crystal run ./twist_and_shout.cr -- -t
    3. Group members:
    4. ==============
    5. JOHN LENNON
    6. PAUL MCCARTNEY
    7. GEORGE HARRISON
    8. RINGO STARR

    Parameterized options

    Let’s create another application: when passing the option -g / --goodbye_hello, the application will say hello to a given name passed as a parameter to the option.

    ```crystal title=”hello_goodbye.cr” require “option_parser”

    option_parser = OptionParser.parse do |parser| parser.banner = “Welcome to The Beatles App!”

    parser.on “-v”, “—version”, “Show version” do puts “version 1.0” exit end parser.on “-h”, “—help”, “Show help” do puts parser exit end parser.on “-g NAME”, “—goodbye_hello=NAME”, “Say hello to whoever you want” do |name| say_hi_to = name end end

    unless say_hi_to.empty? puts “” puts “You say goodbye, and #{the_beatles.sample} says hello to #{say_hi_to}!” end

    Great! These applications look awesome! But, what happens when we pass an option that is not declared? For example -n

    1. $ crystal run ./hello_goodbye.cr -- -n
    2. Unhandled exception: Invalid option: -n (OptionParser::InvalidOption)
    3. from ...

    Oh no! It’s broken: we need to handle invalid options and invalid parameters given to an option! For these two situations, the OptionParser class has two methods: #invalid_option and #missing_option

    So, let’s add this option handler and merge all these CLI applications into one fabulous CLI application!

    All My CLI: The complete application!

    Here’s the final result, with invalid/missing options handling, plus other new options:

    ```crystal title=”all_my_cli.cr” require “option_parser”

    the_beatles = [ “John Lennon”, “Paul McCartney”, “George Harrison”, “Ringo Starr”, ] shout = false say_hi_to = “” strawberry = false

    option_parser = OptionParser.parse do |parser| parser.banner = “Welcome to The Beatles App!”

    parser.on “-v”, “—version”, “Show version” do puts “version 1.0” exit end parser.on “-h”, “—help”, “Show help” do puts parser exit end parser.on “-t”, “—twist”, “Twist and SHOUT” do shout = true end parser.on “-g NAME”, “—goodbye_hello=NAME”, “Say hello to whoever you want” do |name| say_hi_to = name end parser.on “-r”, “—random_goodbye_hello”, “Say hello to one random member” do say_hi_to = the_beatles.sample end parser.on “-s”, “—strawberry”, “Strawberry fields forever mode ON” do strawberry = true end parser.missing_option do |option_flag| STDERR.puts “ERROR: #{option_flag} is missing something.” STDERR.puts “” STDERR.puts parser exit(1) end parser.invalid_option do |option_flag| STDERR.puts “ERROR: #{option_flag} is not a valid option.” STDERR.puts parser exit(1) end end

    members = the_beatles members = the_beatles.map &.upcase if shout

    puts “Strawberry fields forever mode ON” if strawberry

    puts “” puts “Group members:” puts “==============” members.each do |member| puts “#{strawberry ? “🍓” : “-“} #{member}” end

    unless say_hi_to.empty? puts “” puts “You say goodbye, and I say hello to #{say_hi_to}!” end

    1. Sometimes, we may need the user to input a value. How do we _read_ that value?
    2. Easy, peasy! Lets create a new application: the Fab Four will sing with us any phrase we want. When running the application, it will request a phrase to the user and the magic will happen!
    3. ```crystal title="let_it_cli.cr"
    4. puts "Welcome to The Beatles Sing-Along version 1.0!"
    5. puts "Enter a phrase you want The Beatles to sing"
    6. print "> "
    7. user_input = gets
    8. puts "The Beatles are singing: 🎵#{user_input}🎶🎸🥁"

    The method gets will pause the execution of the application until the user finishes entering the input (pressing the Enter key). When the user presses Enter, then the execution will continue and user_input will have the user value.

    But what happens if the user doesn’t enter any value? In that case, we would get an empty string (if the user only presses ) or maybe a Nil value (if the input stream is closed, e.g. by pressing Ctrl+D). To illustrate the problem let’s try the following: we want the input entered by the user to be sung loudly:

    ```crystal title=”let_it_cli.cr” puts “Welcome to The Beatles Sing-Along version 1.0!” puts “Enter a phrase you want The Beatles to sing” print “> “ user_input = gets puts “The Beatles are singing: 🎵#{user_input.upcase}🎶🎸🥁”

    Ah! We should have known better: the type of the user input is the String | Nil. So, we have to test for Nil and for empty and act naturally for each case:

    exit if user_input.nil? # Ctrl+D

    default_lyrics = “Na, na, na, na-na-na na” \ “ / “ \ “Na-na-na na, hey Jude”

    lyrics = user_input.presence || default_lyrics

    puts “The Beatles are singing: 🎵#{lyrics.upcase}🎶🎸🥁”

    1. ## Output
    2. Now, we will focus on the second main topic: our applications output.
    3. For starters, our applications already display information but (I think) we could do better. Lets add more _life_ (i.e. colors!) to the outputs.
    4. And to accomplish this, we will be using the [`Colorize`](https://crystal-lang.org/api/Colorize.html) module.
    5. Lets build a really simple application that shows a string with colors! We will use a yellow font on a black background:
    6. ```crystal title="yellow_cli.cr"
    7. require "colorize"

    Great! That was easy! Now imagine using this string as the banner for our All My CLI application, it’s easy if you try:

    1. parser.banner = "#{"The Beatles".colorize(:yellow).on(:black)} App"

    For our second application, we will add a text decoration (blinkin this case):

    ```crystal title=”let_it_cli.cr” require “colorize”

    puts “Welcome to The Beatles Sing-Along version 1.0!” puts “Enter a phrase you want The Beatles to sing” print “> “ user_input = gets

    exit if user_input.nil? # Ctrl+D

    default_lyrics = “Na, na, na, na-na-na na” \ “ / “ \ “Na-na-na na, hey Jude”

    lyrics = user_input.presence || default_lyrics

    puts “The Beatles are singing: #{“🎵#{lyrics}🎶🎸🥁”.colorize.mode(:blink)}” ```

    Let’s try the renewed application … and hear the difference!! Now we have two fabulous apps!!

    You may find a list of available colors and text decorations in the API documentation.

    As with any other application, at some point, we would like to for the different features.

    Right now the code containing the logic of each of the applications always gets executed with the OptionParser, i.e. there is no way to include that file without running the whole application. So first we would need to refactor the code, separating the code necessary for parsing options from the logic. Once the refactoring is done, we could start testing the logic and including the file with the logic in the testing files we need. We leave this as an exercise for the reader.

    In case we want to build richer CLI applications, there are libraries that can help us. Here we will name two well-known libraries: Readline and NCurses.

    As stated in the documentation for the GNU Readline Library, Readline is a library that provides a set of functions for use by applications that allow users to edit command lines as they are typed in. Readline has some great features: filename autocompletion out of the box; custom auto-completion method; keybinding, just to mention a few. If we want to try it then the shard will give us an easy API to use Readline.

    On the other hand, we have NCurses(New Curses). This library allows developers to create graphical user interfaces in the terminal. As its name implies, it is an improved version of the library named Curses, which was developed to support a text-based dungeon-crawling adventure game called Rogue! As you can imagine, there are already a couple of shards in the ecosystem that will allow us to use NCurses in Crystal!