Building Modern CLI Applications in Go - Marian Montagnino - E-Book

Building Modern CLI Applications in Go E-Book

Marian Montagnino

0,0
37,99 €

-100%
Sammeln Sie Punkte in unserem Gutscheinprogramm und kaufen Sie E-Books und Hörbücher mit bis zu 100% Rabatt.

Mehr erfahren.
Beschreibung

Evolve the humble CLI using Go and unleash the next generation of powerful, flexible, and empathy-driven interfaces



Purchase of the print or Kindle book includes a free PDF eBook

Key Features



  • Discover how Go enables the development of elegant and intuitive CLIs
  • Explore a range of CLI development aspects and pick up a vast array of best practices
  • Create engaging and user-friendly interfaces and learn how to distribute them

Book Description



Although graphical user interfaces (GUIs) are intuitive and user-friendly, nothing beats a command-line interface (CLI) when it comes to productivity. Many organizations settle for a GUI without searching for alternatives that offer better accessibility and functionality. If this describes your organization, then pick up this book and get them to rethink that decision.



Building Modern CLI Applications in Go will help you achieve an interface that rivals a GUI in elegance yet surpasses it in high-performance execution. Through its practical, step-by-step approach, you'll learn everything you need to harness the power and simplicity of the Go language to build CLI applications that revolutionize the way you work.



After a primer on CLI standards and Go, you'll be launched into tool design and proper framework use for true development proficiency. The book then moves on to all things CLI, helping you master everything from arguments and flags to errors and API calls. Later, you'll dive into the nuances of empathic development so that you can ensure the best UX possible, before you finish up with build tags, cross-compilation, and container-based distribution.



By the end of this UX book, you'll be fully equipped to take the performance and flexibility of your organization's applications to the next level.

What you will learn



  • Master the Go code structure, testing, and other essentials
  • Add a colorful dashboard to your CLI using engaging ASCII banners
  • Use Cobra, Viper, and other frameworks to give your CLI an edge
  • Handle inputs, API commands, errors, and timeouts like a pro
  • Target builds for specific platforms the right way using build tags
  • Build with empathy, using easy bug submission and traceback
  • Containerize, distribute, and publish your CLIs quickly and easily

Who this book is for



This book is for beginner- and intermediate-level Golang developers who take an interest in developing CLIs and enjoy learning by doing. You'll need an understanding of basic Golang programming concepts, but will require no prior knowledge of CLI design and development. This book helps you join a community of CLI developers and distribute within the popular Homebrew package management tool.

Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:

EPUB

Seitenzahl: 444

Veröffentlichungsjahr: 2023

Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Building Modern CLI Applications in Go

Develop next-level CLIs to improve user experience, increase platform usage, and maximize production

Marian Montagnino

BIRMINGHAM—MUMBAI

Building Modern CLI Applications in Go

Copyright © 2023 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

Group Product Manager: Gebin George

Publishing Product Manager: Pooja Yadav

Senior Editor: Nisha Cleetus

Technical Editor: Pradeep Sahu

Copy Editor: Safis Editing

Project Coordinator: Manisha Singh

Proofreader: Safis Editing

Indexer: Hemangini Bari

Production Designer: Joshua Misquitta

Business Development Executive: Kriti Sharma

Developer Relations Marketing Executives: Rayyan Khan & Sonia Chauhan

Production reference: 2080323

Published by Packt Publishing Ltd.

Livery Place

35 Livery Street

Birmingham

B3 2PB, UK.

ISBN 978-1-80461-165-4

www.packtpub.com

Above all, I would like to express my gratitude to the Divine Intelligence, the source of all creation, for all the blessings and opportunities that helped me bring this book to life.

To my dear old dad, Dr. Joseph Montagnino, who always cheered me on in all things math and tech. Without you, I would not be the fearless learning machine I am today.

To my dearest mother, Lilia Banogon Montagnino, thank you for always seeing the potential in me and for being my guiding light throughout life. Your unwavering belief in me has given me the strength to face any challenge and pursue my dreams fearlessly.

To my dear sister, Lillian, your unwavering support and encouragement in self-discovery have allowed me to pursue my passion and find my true creative self.

To my nieces, Carine and Maple, and nephew, Cayden, thank you for the joy and playfulness you bring to my life.

To Julia and her late father, Marc Howe - you and the team at Cyber Warrior, Inc were my tech-savvy guild of wizards to guide and inspire me, and for that, I am forever grateful. Your mentorship and friendship paved the way for me, and I’ll never forget it.

And to all my cherished friends and family - you all keep me grounded, laughing, and full of love. Thank you for being my support system and for making life so darn wonderful.

– Marian Montagnino

Foreword

On November 10, 2009, a historical announcement was made by Robert Griesemer, Rob Pike, Ken Thompson, Ian Taylor, Russ Cox, Jini Kim, and Adam Langley. They announced to the world a new experimental language called Go. It was on this day that the Go programming language was open sourced and available for the world to use. As we wind the clock forward 12 years to 2023, the language is currently ranked as the 12th most popular language in the world on the Tiobe index.

In 2016, the Go team released the results of their annual survey for the first time. One question in the survey asked people what they were writing in Go. Here are the top seven results of that question:

2,247 (63%): A runnable/interactive program (or CLI)2,174 (60%): API/RPC services (returning non-HTML)1,886 (52%): Web services (returning HTML)1,583 (44%): Agents and daemons (e.g, monitoring)1,417 (39%): Libraries or frameworks1,209 (34%): Data processing (pipelines and aggregation)1,120 (31%): Automation/scripts (e.g, deployment and configuration management)

At the time in 2016, CLI tooling was the number one use of Go. 8 years later in the 2022 survey, API/RPC services top the list at 73%, but a close second is CLI tooling at 60%. What might be more exciting is if you search the web for the best languages to use for building command-line tools, Go will always be at the top of any list.

If you have picked up this book because you need to write a command-line tool and you’re thinking about using Go, or you’ve already chosen Go but need help on how to write the best CLI possible, you’ve picked up the right book. Marian is a seasoned software engineer with many years of experience writing CLI tooling for the companies and projects she has worked on. This book is what she wished she had when she was asked to write her first command-line tool.

I’m certain that you will learn what you need from this book and that the book will help jumpstart your knowledge into writing runnable and interactive programs in Go. You can feel confident that what Marian is teaching you will be idiomatic Go, following the best practices, design philosophies, and guidelines known today for CLI development and useability.

William Kennedy

Managing partner at Ardan Labs

Go trainer, writer, and speaker

Contributors

About the author

Marian Montagnino is a Senior Software Engineer at Netflix with over 20 years of experience. Since the early nineties, when her family first got a home computer, she has been using the terminal and command line applications to navigate through text-based systems. In 1995, she held her first job as a SysOp, or system operator, for Real of Mirage BBS in Fair Lawn, NJ. Her early years discovering technology inspired her to continue learning about computers. She received her Dual Computer Science and Mathematics of Operations Research BSc from Rensselaer Polytechnic Institute and her Applied Mathematics MSc from Stevens Institute of Technology.

To the exceptional members of the Go community, particularly Bill Kennedy, Natalie Pistunovich, Mat Ryer, Steven Francia, and Erik St .Martin, your creativity and innovation continue to inspire me towards greatness.

Finally, thank you to the wonderful team at Packt for their help and support throughout the process; I couldn’t have done it without you!

About the reviewers

Wisnu Anggoro is a backend engineer with more than 10 years of experience in developing and maintaining distributed systems. He has expertise in Golang (Go Programming Language) and microservices architecture to solve complex software problems by implementing innovative solutions.

Tushar Sadhwani is a long-term Python and Go developer, open source contributor, author, and speaker. He is currently a Language Engineer at DeepSource, where he works on building linters and code analysis tools for Python. He has been an advocate of static type checking for over three years, and has been contributing to static code analysis tools over the past two.

Table of Contents

Preface

Part 1: Getting Started with a Solid Foundation

1

Understanding CLI Standards

A brief introduction and history of the command line

About the history

Introducing the CLI

The philosophy of CLI development

Checklist for a successful CLI

The guidelines

Name

Help and documentation

Input

Output

Configuration

Security

Open source community

Software lifespan and robustness

Go for CLIs

Summary

Questions

Answers

Further reading

2

Structuring Go Code for CLI Applications

Technical requirements

Commonly used program layouts for robust applications

Program layouts

Common folders

Determining use cases and requirements

Use cases

Requirements

Disadvantages and benefits of use cases and requirements

Use cases, diagrams, and requirements for a CLI

Requirements for a metadata CLI

Structuring an audio metadata CLI application

Bounded context

Language

Entities and value objects

Aggregation

Service

Events

Repository

Creating the structure

Summary

Questions

Answers

Further reading

3

Building an Audio Metadata CLI

Technical requirements

Defining the components

cmd/

cmd/api/

cmd/cli/

cmd/cli/command

extractors/

extractors/tags

extractors/transcript

internal/interfaces

models/

services/metadata

storage/

vendor/

Implementing use cases

Uploading audio

Requesting metadata

Testing a CLI

Manual testing

Testing and mocking

Summary

Questions

Answers

4

Popular Frameworks for Building CLIs

Technical requirements

Cobra – a library for building modern CLI applications

Creating subcommands

Global, local, and required flags

Intelligent suggestions

Automatically generated help and man pages

Powering your CLI

Viper – easy configuration for CLIs

Configuration types

Watching for live config changes

Basic calculator CLI using Cobra and Viper

The Cobra CLI commands

The Viper configuration

Running the basic calculator

Summary

Questions

Answers

Further reading

Part 2: The Ins and Outs of a CLI

5

Defining the Command-Line Process

Technical requirements

Receiving the input and user interaction

Defining subcommands, arguments, and flags

Piping

Signals and control characters

User interaction

Processing data

Returning the resulting output and defining best practices

Summary

Questions

Answers

Further reading

6

Calling External Processes and Handling Errors and Timeouts

Technical requirements

Calling external processes

The os/exec package

Creating commands using the Cmd struct

Running the command

Interacting with REST APIs

Get request

Pagination

Rate limiting

Handling the expected – timeouts and errors

Timeouts with external command processes

Errors or panics with external command processes

Timeouts and other errors with HTTP requests

Summary

Questions

Answers

Further reading

7

Developing for Different Platforms

Technical requirements

Packages for platform-independent functionality

The os package

The time package

The path package

The runtime package

Implementing independent or platform-specific code

Platform-independent code

Platform-specific code

Build tags for targeted platforms

The build package

Build tags

OS-level differences

Summary

Questions

Answers

Further reading

Part 3: Interactivity and Empathic Driven Design

8

Building for Humans versus Machines

Technical requirements

Building for humans versus machines

Is it a TTY?

Designing for a machine

Designing for a human

Increasing information density with ASCII art

Displaying information with tables

Clarifying with emojis

Using color with intention

Spinners and progress bars

Disabling colors

Being consistent across CLIs

Naming

Positional versus flag arguments

Flag naming

Usage

Summary

Questions

Further reading

Answers

9

The Empathic Side of Development

Technical requirements

Rewriting errors to be human-readable

Guidelines for writing error messages

Decorating errors

Customizing errors

Writing better error messages

Providing debug and traceback information

Logging data

Initiating a logger

Implementing a logger

Trying out verbose mode to view stack traces

Effortless bug submission

Help, documentation, and support

Generating help text

Generating man pages

Embedding empathy into your documentation

Summary

Questions

Further reading

Answers

10

Interactivity with Prompts and Terminal Dashboards

Technical requirements

Guiding users with prompts

Designing a useful terminal dashboard

Learning about Termdash

Implementing a terminal dashboard

Creating the terminal layer

Creating the infrastructure layer

Creating the container layer

Creating the widgets layer

Summary

Questions

Answers

Further reading

Part 4: Building and Distributing for Different Platforms

11

Custom Builds and Testing CLI Commands

Technical requirements

What are build tags and how can you use them?

How to utilize build tags

Creating a pro, free, and dev version

Adding build tags to enable pprof

Building with tags

Building a free version

Building a pro version

Building to enable pprof on the pro version

Testing CLI commands

Mocking the HTTP client

Handling test configuration

Creating a test for a command

Running the tests

Summary

Questions

Answers

Further reading

12

Cross-Compilation across Different Platforms

Technical requirements

Manual compilation versus build automation tools

Using GOOS and GOARCH

Compiling for Linux, macOS, and Windows

Building using tags

Building using the GOOS environment variable

Building using the GOARCH environment variable

Installing using tags and GOOS env va

Installing using tags and GOARCH env var

Scripting to compile for multiple platforms

Creating a bash script to compile in Darwin or Linux

Creating a PowerShell script in Windows

Summary

Questions

Answers

Further reading

13

Using Containers for Distribution

Technical requirement

Why use containers?

Benefiting from containers

Deciding not to use containers

Building a simple Docker image

Running a simple Docker container

Testing with containers

Creating the integration test file

Writing the integration tests

Writing the Dockerfiles

Distributing with containers

Building a new image to run as an executable

Interacting with your container as an executable

Mapping host machine to container file paths

Reducing image size by using multi-stage builds

Distributing your Docker image

Depending on Docker

Summary

Questions

Further reading

Answers

14

Publishing Your Go Binary as a Homebrew Formula with GoReleaser

Technical requirements

GoReleaser workflow

Defining the workflow

Trigger release

Tag and push the code

Installing with Homebrew and Testing

Summary

Questions

Further reading

Answers

Index

Other Books You May Enjoy

Part 1: Getting Started with a Solid Foundation

This part covers the Command-Line Interface (CLI) and its resurgence in popularity. The history, anatomy, and design principles of the CLI are discussed, with a focus on UNIX’s philosophy and the benefits of using Go to build a CLI. The guide offers a step-by-step approach to building a new application, including code structure, domain-driven design, and an example audio metadata CLI application. Hands-on learning is encouraged with the example audio metadata CLI application, and popular frameworks, such as Cobra and Viper, are explored to speed up the development process. Overall, this part provides a comprehensive overview of the CLI and its practical applications in modern programming, offering valuable guidance to developers looking to build efficient and effective CLI applications.

This part has the following chapters:

Chapter 1, Understanding CLI StandardsChapter 2, Structuring Go Code for CLI ApplicationsChapter 3, Building an Audio Metadata CLIChapter 4, Popular Frameworks for Building CLIs

1

Understanding CLI Standards

The Command-Line Interface (CLI) is a text-based interface for humans, and computer interaction was initially designed as a way of interacting with an Operating System (OS) before the desktop graphical interface was invented. The CLI, as we know it today, was in popular use in the 1960s until the graphical desktop interface was developed a decade later. However, although most computer users are used to the graphical user interface (GUI) and web, there’s been a resurgence of CLI development circa 2017. Popular and new use cases for the retro CLI vary, but its most popular usage is as an additional offering alongside a company’s API for increased platform usage.

In this chapter, you will learn about the comprehensive history of the CLI, what it is today, and a breakdown of its anatomy. You will learn about UNIX’s philosophy and how following its principles will guide you toward the creation of a successful CLI.

By the end of this chapter, you’ll have a deeper understanding of the CLI, how best to design and implement proven, time-tested standards, and why Go, which has become an increasingly popular language, has a compelling case for being the best language to build your CLI.

In this chapter, we are going to cover the following main topics:

A brief introduction and history of the command lineThe philosophy of CLI developmentModern CLI guidelinesGo for CLIs

A brief introduction and history of the command line

The CLI is the result of the evolution of a much broader human-computer interaction, specifically communication and language processing. Let’s begin the story with the creation of the first compiler, which took us from using punch cards to programming languages.

About the history

The first computer compiler was written by Grace Hopper. A compiler was able to translate written code to machine code and lifted the great burden off programmers of the time consumption of writing machine code manually. Grace Hopper also invented the COBOL programming language in 1959. In that era, punch cards were used for data processing applications or to control machinery. They would contain COBOL, Fortran, and Assembly code. The compiler and advancement of new programming languages eased the task of programming.

The same year, the microchip was invented by Jack Kilby and Robert Noyce. Much less expensive, small-scale computers were made possible and, finally, human-in-the-loop, a back-and-forth interaction between the human and computer, became feasible. Computers were now multitasking and time-sharing systems.

At this point, keyboards became the main method of interacting with computers. By the early 1960s, engineers had attached a Cathode Ray Tube (CRT) monitor to the TeleTYpewriter (TTY) machine. This combination of the CRT and TTY was called a glass TTY and marked the beginning of what we consider the modern monitor.

In 1966, the CRT and teletype machine, which combined the technologies of the electric telegraph, telephone, and typewriter, were about to merge with the final missing puzzle piece, the computer. The teletype computer interface was born. Users would type a command, hit the Enter key, and the computer would respond. These were called command-line interfaces!

There were so many more exciting developments that followed, from the invention of ASCII characters in 1963 to the internet in 1969, UNIX in 1971, and email in 1972. Lexical analysis parsers in 1975 played a major part in the development of programming languages. Text-based adventure games provided amusement for the tech savvy by 1977, and the beginnings of the GUI emerged in the 1970s.

A network of these computers would not have been possible if not for the evolution of the telephone. In 1964, the acoustic modulator/demodulator (modem) was used to transmit data between a telephone line and a computer. The acoustic modem brought us the wide area network (WAN), local area network (LAN), and the broadband we know of today. LAN parties peaked in the 1990s and carried on well into the early 2000s.

In 1978, the first public dial-up bulletin board system (BBS) was developed by Ward Christensen and Randy Suess, who also created the computerized bulletin board system (CBBS). With a modem, users could dial into servers running the CBBS software and connect via a terminal program. Throughout the 1980s, BBS’s popularity grew to fantastic heights, and even in the mid-1990s, BBSs served the greater collective market compared to emerging online service providers such as CompuServe and America Online (AOL).

Note

This deeper understanding of the history of the CLI may give you a greater appreciation for what it is. The terminal is a bit of a time machine. The use of many UNIX and DOS commands feels like you’re standing on the shoulders of giants, looking down at the long computing history beneath it.

Introducing the CLI

Based on the history of the CLI, it’s clear to see that it is a text-based interface that allows communication from user to computer, computer to computer, and computer back to user. It requires the same specific instructions and clear language as the earlier machines it evolved from. Now, let’s dig deeper into CLIs to learn about the different kinds, how they are generally structured, and how and why they are used.

Anatomy

For any CLI, regardless of the specific type, it’s important to understand the anatomy of the commands themselves. Without a particular structure, the computer would not be able to properly parse and understand its instructions. The following is a simple example that we will use to distinguish the different components of a command:

~ cat -b transcript

In the context of this example, the UNIX command, cat, is used to view the contents of the file, transcript. The addition of the –b flag tells the command to print the line number next to non-empty output lines. We will go into each component of the command in detail in the following subsections.

The prompt

A symbol on the terminal indicates to the user that the computer is ready to receive a command. The preceding example shows ~ as the command prompt; however, this can differ depending on the OS.

The command

There are two types of commands:

Internal commands are commands that are built into the OS shell, stored within internal memory, and execute faster. A few examples include folder and environment manipulation commands, such as cd, date, and time commands. They do not require a search of the PATH variable to find the executable.External commands are commands that are not built into the OS and are only available through software or applications installed by other parties. These commands are stored within secondary memory and do not execute as quickly as internal commands. A few examples include ls and cat. These are usually located in /bin or /usr/bin in UNIX and require a search of the PATH variable to find the executable.

The previous example uses cat as the external command.

Arguments and options

Commands usually take in parameters for input that consist of one or many arguments and/or options:

Arguments are parameters that pass information to the command, for example, mkdir test/.

In the preceding code snippet, test/ is the input parameter to the mkdir command.

Options are flags, or switches, that modify the operation of a command, for example mkdir -p test/files/.

In the preceding example, -p is an option to make parent directories if needed.

In the example at the start of this section, -b is an optional flag, shorthand for --number-nonblank, which tells the command to print the line number next to non-empty lines, and the filename, transcript, is an argument passed into the command.

Whitespace

For the OS or application to properly parse these commands, arguments, and options, each is delimited by whitespace. Special attention must be paid to the fact that whitespaces may exist within the parameter itself. This can cause a bit of ambiguity for the command-line interpreter.

Take care to resolve this ambiguity by replacing spaces within parameters. In the following example, we replace the spaces with underscores:

cat Screen_Shot_2021-06-05_at_10.23.16_PM.png

You can also decide to put quotes around the parameter, as in the following example:

cat "Screen Shot 2021-06-05 at 10.23.16 PM.png"

Finally, resolve ambiguity by adding an escape character before each space, as in the following example:

cat Screen\ Shot\ 2021-06-05\ at\ 10.23.16\ PM.png

Note

Although whitespace is the most widely used delimiter, it is not universal.

Syntax and semantics

The CLI provides the language for communicating with the OS or application. Thus, like any language, to be properly interpreted, it requires syntax and semantics. The syntax is the grammar defined by the OS or the application vendor. Semantics define what operations are possible.

When you look at some command-line applications, you can see the language being used. Sometimes, the syntax differs between tools; I will go over the specifics later in this chapter, but, for example, cat -b transcript is a command we’ve looked at before. The command, cat, is a verb, the flag, -b, is an adjective, and transcript is a noun. This is the defined syntax of the cat UNIX command: verb, adjective, noun.

The semantics of the command are defined by what operations are possible. You can see this by viewing the options of, for example, the cat command, which are usually shown in the usage section of the help page, which is output when a user uses the application incorrectly.

Help pages

Because the CLI is entirely text-based and lacking in visual cues, its usage may be ambiguous or unknown. A help page is essential to every CLI. To view a list of valid parameters and options, users may run the command followed by the help option, typically -help, --help, or -h. The -h option is an example of an abbreviated shortcut for the help command.

There’s a common syntax used in built-in help and man pages and following this standard will allow users familiar with the standard to easily use your CLI:

Required parameters are typically represented within angled brackets, for example, ping <hostname>Optional parameters are represented within square brackets, for example, mkdir [option] <dirname>Ellipses represent repeated items, for example, cp [option]... <source>... <directory>Vertical bars represent a choice of items, for example, netstat {-t | -u}

Usage

The CLI was the first interface between the user and the OS used primarily for numerical computation, but in time, its usage has expanded in many more practical and fun ways.

Let us see some of the uses:

Editor MACroS (Emacs), one of the earliest forms of a text editor provided in UNIX, is a CLI in the form of a mini buffer. Commands and arguments are entered as a combination of key presses: either a Ctrl character plus a key or a key prefixed by a Ctrl character and the output displayed within another buffer.Read-Evaluate-Print Loop (REPL) is a Python interactive shell that offers a CLI, and according to its name can read, evaluate, print, and loop. It allows users a play environment to validate Python commands.MajorMUD and Lunatix are just a couple of popular games that were available on bulletin board systems. As soon as programmers could turn CLIs into play, they did, and while these games were entirely text-based, they were certainly not lacking in fun!Modern video games call their CLI a gaming console. From the console, mod developers can run commands to debug, cheat, or skip part of the game.Helper programs often take in parameters to launch a program in a particular way. For example, Microsoft Visual Code has a command-line option: code <filename>.Some CLIs are embedded into a web application, for example, web-based SSH programs.Companies such as AWS offer CLIs alongside their API as an additional way of interacting with their platform.

Last but not least, scripting has allowed engineers to take their CLIs to a more interactive level. Within a shell scripting language, programmers can script calls to the CLI and capture and manipulate output. The output of one command may also be passed as input into another. This makes the CLI a very powerful resource for developers.

Types

There are two main types of CLIs:

OS CLIsApplication CLIs

OS CLIs

OS CLIs are often provided alongside the OS. This kind of CLI is referred to as a shell. It is the command-line interpreter that sits a layer above the kernel interpreting and processing commands entered by the user and outputting results and a text-based method of interacting with the OS as an alternative to the graphical display.

Application CLIs

The second type of CLI allows interaction with a specific application running on the OS.

There are three main types of ways users may interact with an application’s CLI:

Parameters: They provide input to launch the application in a particular wayInteractive command-line sessions: They are launched after the application as an independent and text-alternative method of controlInter-process communication: This allows users to stream or pipe data from the output of one program into another

GUI versus CLI example

Let’s give a clear example of how the CLI can reign over the GUI in speed. Suppose we have a folder full of screenshots. The names of each contain a space and we’d like to rename these files to replace the whitespace with an underscore.

GUI

With a GUI, there’d be several manual steps for renaming a folder full of screenshots that contain whitespaces throughout the filename. Let’s show these steps within macOS, or Darwin:

First, we’d need to open the folder containing all the screenshots:

Figure 1.1 – Folder containing the screenshots where each filename contains numerous spaces

Second, we’d press the control button and left-click on a filename, then from the context menu that pops up, select the Rename option.

Figure 1.2 – From the context menu, click on the Rename option

Finally, manually replace each of the whitespaces with an underscore.

Figure 1.3 – Replaced whitespaces with underscores in filename

Repeat steps 1-3 for each file in the folder. We’re lucky this folder only contains four screenshots. It can quickly get repetitive and tiresome with a folder of more files.

CLI

Let’s see how much faster the CLI can be. Let’s open the terminal and navigate to the folder with the files:

cd ~/Desktop/screenshots/

Let’s view what currently exists in the folder by typing the ls command:

mmontagnino@Marians-MacBook-Pro screenshots % ls Screen Shot 2022-12-20 at 10.27.55 PM.png Screen Shot 2022-12-20 at 10.32.48 PM.png Screen Shot 2022-12-26 at 5.24.48 PM.png Screen Shot 2022-12-27 at 12.08.12 AM.png

Let’s run a cleverly crafted command that loops through each file in the current directory and renames it (using mv) to translate the whitespace to an underscore:

for file in *; do mv "$file" `echo $file | tr ' ' '_'` ; done

Let’s run the ls command again to see what’s changed:

mmontagnino@Marians-MacBook-Pro screenshots % ls Screen_Shot_2022-12-20_at_10.27.55_PM.png Screen_Shot_2022-12-20_at_10.32.48_PM.png Screen_Shot_2022-12-26_at_5.24.48_PM.png Screen_Shot_2022-12-27_at_12.08.12_AM.png

Wow! We’ve just run a single command and the files are automatically renamed! This is just one example to show the power of CLIs and how much faster tasks can be executed compared to a GUI.

The comeback

The reason there’s been a comeback of the CLI within recent years is because of these many benefits. The GUI can be resource-intensive, tedious when performing repetitive tasks, and sometimes slow.

The CLI, on the other end of the spectrum, is lightweight, scriptable, and fast. The advantages don’t end there. The CLI might even offer commands and parameters that are not available, or are unthinkable, within the GUI. There’s much to be admired and it’s also a little mysterious.

I’m crushing a little on the CLI here! Jokes aside, to be fair to the attractive GUI, it has visual cues that allow the user to be self-guided. The all-too-mysterious CLI, on the other hand, requires help and man pages to understand its available parameters and options.

Though it may appear difficult to understand, once understood, the power of the CLI becomes apparent and inspiring.

The philosophy of CLI development

Philosophy plays a major role in the development of computer science. Throughout history, there have been many great contributions to computer science through philosophy, partially because many computer scientists were and are also philosophers. It is no surprise that each OS has its own distinct philosophy.

Windows, for example, hardcodes most of its intelligence within the program or OS, assuming users’ ignorance and limiting their flexibility. Although the barrier to understanding is lower, users interact with the program without understanding how it works.

The developers of UNIX had an opposing philosophy: provide the user with almost limitless possibilities to empower them. Although the learning curve is steep, much more can be developed within an environment that doesn’t shield its users from the complexity of freedom.

There have been many books written about UNIX’s philosophy and implementing it in real life is an art form. I am sure, therefore, many people view coding as a craft. Although there are many other philosophies to review, the focus in this section will be on UNIX’s philosophy.

The legendary designers of the Go programming language, Ken Thompson, Robert Griesemer, and Rob Pike, share a long history with UNIX, and it feels fitting to discuss the philosophy within the context of its creators since Go was built around it.

UNIX’s philosophy advocates for simple and modular designs that are both extensible and composable. The basis is that the relationships between numerous small programs are more powerful than the programs themselves. For example, many UNIX programs handle simple tasks in isolation, but when combined, these simple tools can be orchestrated in a very powerful way.

Checklist for a successful CLI

The following are some principles inspired by this UNIX philosophy that when followed will help create a successful CLI:

Building a modular program

Design your CLI with standardization in mind to ensure it can be easily composed with other applications. Specifically utilizing standard in and out, standardized errors, signals, and exit codes helps to build a program that is both modular and easily composable. Composability can be handled simply with pipes and shell scripts, but there are also programming languages that can help piece programs together. Continuous Integration/Continuous Delivery (CI/CD), orchestration, and configuration management tools are often built on top of command-line execution and scripts to automate code integration or deployment or to configure machines. Consider the data output from your program and how easily composable it is. The best options are plain text or JSON when structure is needed.

Building for humans first

The first CLI commands were written with the assumption that they’d only be used by other programs. This is no longer the case, and so programs should be built with humans first in mind.

Conversation will be the main method of human-computer interaction. Imagine the natural flow of human conversation and how that concept can be applied to help a user who has misunderstood the program design. In natural language, your program can suggest possible corrections, the current state in a multi-step process, and request confirmation before continuing to do something risky. In the best-case scenario, your user has had a pleasant experience with your CLI, feeling empowered to discover operations and receiving assistance when needed. In the worst-case scenario, your user feels ignored and frustrated with no help in sight. Don’t be that CLI!

Finally, write readable code so other developers can easily maintain your program in the future.

Separating interfaces from engines and policies from mechanisms

Decoupling these allows different applications to use the same engine through interfaces or use the same mechanism with different policies.

Keeping it simple

Only add complexity when it’s necessary. When complexity does occur, fold it into the data instead of the logic. Where usability is not compromised, use existing patterns.

Staying small

Don’t write a big program unless there’s no other way.

Being transparent

Be as transparent as possible so users can understand how to use the program and what’s going on. Transparent programs have comprehensive help texts and provide lots of examples allowing users to easily discover the parameters and options they need and have the confidence to execute them. The GUI certainly has a leg up in terms of transparency and visibility; however, we can learn from it and see what can be incorporated to make the CLI easier to learn and use. Users resorting to Google or Stack Overflow is an anti-pattern here.

Being robust

Robustness is the result of the former principle: transparency and simplicity. The program should work in a way that the user expects, and when errors occur, explain what is happening clearly with suggestions for resolution. Immediately printing stack traces or not informing the user with a clear and immediate response leaves the user feeling like they are on shaky ground.

No surprises

Keep your program intuitive by building on top of a user’s existing knowledge. For example, a logical operator such as + should always mean addition and - should always mean subtraction. Make your program intuitive by staying consistent with pre-existing knowledge and patterns of behavior.

Being succinct

Don’t print output unnecessarily and don’t be completely silent, leaving the user to wonder what’s going on. There’s a balance in communication required to say exactly what needs to be said; no more, no less. Too much is a large block of verbose text that forces the user to parse through it to find useful information. Too little is when the command prompt hangs in silence leaving the user to assume a state about the program.

Failing noisily

Repair what can be repaired, and when the program fails, fail noisily and as soon as possible. This will prevent incorrect output from corrupting other programs depending on it.

Saving your time

Build code to save developers’ time as opposed to the machine’s time, which is relatively cheap these days. Also, write programs that generate programs. It’s much faster and less error-prone for computers to generate code over hand-hacking.

Building a prototype first, then optimizing

Sometimes, programmers spend too much time optimizing early on for marginal gains. First, get it working, and then polish it.

Building flexible programs

Programs may be used in ways the developers did not intend. Therefore, making the design flexible and open will allow the program to be used in ways unintended.

Designing for extensibility

Extend the lifespan of your program by allowing protocols to be extensible.

Being a good CLI citizen

Bring empathy into the design and peacefully coexist with the rest of the CLI ecosystem.

The philosophy directly influences the guidelines for creating a CLI. In the next section, you will clearly see the link to satisfy the philosophy tenets discussed, and if anything, following the guidelines will increase the odds of creating a successful CLI.

The guidelines

These guidelines have been formulated since the first CLI and have continued to evolve through the many years of developer and user experience. Following these guidelines will increase your chances of CLI success; however, there may be times when you decide to go your own way and follow an anti-pattern. There could be many reasons to choose an unconventional route. Remember that these are just guidelines and there are no hard and fast rules.

For life, and building CLIs, to be fun, we must allow a little chaos and the freedom necessary to be creative.

Name

The name of the CLI holds significant weight as the name may convey symbolic ideas beyond the initial intention. People do not like to think more than necessary, so it’s best to choose a name that is simple, memorable, and easy to pronounce. It’s amazing how many CLI program names have been chosen so arbitrarily without much thought.

There have been studies that support the linguistic Heisenberg principle: labeling a concept changes how people perceive it.

Hence, keep it short and easy to type. Use entirely lowercase variables in the name and only use dashes when absolutely necessary.

Some of my favorite application names are clear in the way that they plainly describe the application’s purpose in a creative manner. For example, Homebrew is a package manager for installing applications on macOS. A brew is a concoction of various ingredients, like a recipe, or in this particular case, a formula, to describe how to install an application. Another great example is imagemagick, a command-line application that lets you read, process, or create images magically! Truly, as Arthur C. Clark writes, “Any sufficiently advanced technology is indistinguishable from magic.” Other internal commands we are familiar with are mkdir, for make directory, rm, for remove, and mv, for move. Their popularity is partially a result of the transparent nature of their names, rendering them nearly unforgettable.

Help and documentation

One of the tenets of the UNIX philosophy is transparency, possible mainly through the help and documentation present within the CLI. For new users of the CLI that are in discovery mode, the help and documentation are one of the first sections they will visit. There are a few guidelines to make the help and documentation more easily accessible to the user.

Help

It is a good practice to display help by default when just the command name is entered or with either the -h or –help flag. When you display the help text, make sure it’s formatted and concise with the most frequently used arguments and flag options at the top. Offer usage examples, and if a user misuses the CLI, the program can guess what the user tried to attempt, providing suggestions and next steps.

Documentation

Provide either man pages or terminal-based or web-based documentation, which can provide additional examples of usage. These types of documentation may be linked from the help page as an extension of the resources for gaining an understanding of how the CLI works.

Support

Oftentimes, users will have suggestions or questions on how to use the CLI. Providing a support path for feedback and questions will allow users to give the CLI designer a new perspective on the usage of their CLI. When collecting analytics, be transparent and don’t collect users’ address, phone, or usage data without consent.

Input

There are several ways a CLI retrieves input, but mainly through arguments, flags, and subcommands. There is a general preference for using flags over arguments and making the default the right thing for most users.

Flags

The guideline for flags is that ideally, there exists a full-length version for all flags. For example, -h has --help. Only use –, a single dash, or shorthand notation for commonly used flags and use standard names where there is one.

The following is a list of some standard flags that already exist:

Flag

Usage

-a, --all

All

-d, –debug

Debug

-f, --force

Force

--json

Display JSON output

-h, --help

Help

--no-input

Disable prompt and interactivity

-o, --output

Output file

-p, --port

Port

-q, --quiet

Quiet mode

-u, --user

User

--version

Version

-v

Version or verbose

-d

Verbose

Table 1.1: Standard flags

Arguments

Multiple arguments are fine for simple actions taken on several files. For example, the rm command runs against more than one file. Although, if there exist two or more arguments for different things, you might need to rethink the structure of your command and choose a flag option over an additional argument.

Subcommands

The guidelines for subcommands are that they remain consistent and unambiguous. Be consistent with the structure of subcommands; either noun-verb or verb-noun order works, but noun-verb is much more common. Sometimes, a program offers ambiguous subcommands, such as apt update versus apt upgrade, which causes many, including myself, confusion. Try to avoid this!

Validate the user’s input early, and if it’s invalid, fail early before anything bad happens. Later in this book, we will guide you through using Cobra, a popular and highly recommended command-line parser for Go, to validate user input.

Output

Because CLIs are built for humans and machines, we need to consider that output must be easily consumed by both. I will break down guidelines for both stdout and stderr streams for both humans and machines. Standard output, stdout, is the default file descriptor where a process can write output, and standard error, stderr, is the default file descriptor where a process can write error messages:

stdout

A guideline for standard output for humans is to make the responses clear, brief, and comprehensible for the user. Utilize ASCII art, symbols, emojis, and color to improve information density. Finally, consider simple machine-readable output where usability is not impacted.