System Integration

{sample: true}

Chapter 10: Command-line Rosetta Stone

Learning Objectives

By the end of this chapter, you will be able to:

  • Compare CLI frameworks across languages: Understand similarities and differences between Python Click and CLI frameworks in other languages
  • Translate CLI concepts: Apply Python CLI development knowledge to other programming languages
  • Evaluate language-specific trade-offs: Make informed decisions about which language to use for different CLI projects
  • Migrate between platforms: Understand patterns for porting CLI applications between different languages

Prerequisites

  • Previous Chapters: Chapters 1-9 (Complete Python CLI development foundation)
  • Multi-language Awareness: Basic familiarity with programming concepts in multiple languages
  • CLI Design Principles: Understanding of universal command-line interface design patterns

Chapter Overview

Estimated Time: 60 minutes
Hands-on Labs: 1 cross-language CLI comparison workshop
Assessment: 5-question knowledge check

This chapter provides a comparative analysis of CLI development across multiple programming languages, helping you understand how Python Click concepts translate to other ecosystems and when different languages might be more appropriate for specific CLI applications.


This chapter serves as a guidebook for users coming from another language. The
source code can be found here. The
same style, hello world command-line tool is written in many languages.

R Hello World

This step is an example of a hello world command-line tool in R. The
source code is here.

#!/usr/bin/env Rscript
#Hello World R command-line tool
#


suppressPackageStartupMessages(library("optparse"))
parser <- OptionParser()
parser <- add_option(parser, c("-c", "--count"), type = "integer",
                     help = "Number of times to print phrase",
                     metavar = "number")
parser <- add_option(parser, c("-p", "--phrase"),
                    help = "Phrase to print")

args <- parse_args(parser)


# Function to Generate Phrases
phrasegen <- function(arguments){
    for (count in 1:arguments$count) {
        cat(paste(arguments$phrase, "\n"))
    }
}

#Run the program
phrasegen(args)

Depends on https://github.com/trevorld/optparse for R

Usage

$  hello-world git:(master) $ ./hello-world.R --count 5 --phrase "hello world"
hello world
hello world
hello world
hello world
hello world

Bash Hello World

This step is a hello world Bash example. The
source code is here.

#!/bin/bash
#output looks like this:
#
#  $hello-world git:(master) $ ./hello-world.sh --count 5 --phrase "hello world"
#hello world
#hello world
#hello world
#hello world
#hello world

#Generate phrase "N" times
phrase_generator() {
    for ((i=0; i<$1;i++)); do
        echo "$2"
    done
}

#Parse Options
while [[ $# -gt 1 ]]
do
key="$1"

case $key in
    -c|--count)
    COUNT="$2"
    shift
    ;;
    -p|--phrase)
    PHRASE="$2"
    shift
    ;;
esac
shift
done

#Run program
phrase_generator "${COUNT}" "${PHRASE}"

To lint use make lint. And now run it:

$  hello-world git:(master) $ ./hello-world.rb --count 5 --phrase "hello world"
hello world
hello world
hello world
hello world
hello world

Environment

You may need to also do:

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.zshenv
$ echo 'eval "$(rbenv init -)"' >> ~/.zshenv
$ echo 'source $HOME/.zshenv' >> ~/.zshrc
$ exec $SHELL

Go Hello World

The go
source code is here.

package main

import (
    "fmt"
    "gopkg.in/urfave/cli.v1" // imports as package "cli"
    "os"
)

func main() {
    app := cli.NewApp()
    app.Flags = []cli.Flag{
        cli.StringFlag{
            Name:  "phrase",
            Usage: "Print phrase",
        },
        cli.Int64Flag{
            Name:  "count",
            Usage: "Count to print a phrase",
        },
    }

    app.Action = func(c *cli.Context) error {
        sum := 0
        for i := 0; i < c.Int("count"); i++ {
            sum -= i
            fmt.Println(c.String("phrase"))
        }
        return nil
    }

    app.Run(os.Args)
}

Running program

Run make all

Then run program:

$  hello-world git:(master) $ hello-world --phrase "hello world" --count 5
hello world
hello world
hello world
hello world
hello world

Environment

Setting these variables:

export GOPATH="${HOME}/.go"
export GOROOT="$(brew --prefix golang)/libexec"
export PATH="$PATH:${GOPATH}/bin:${GOROOT}/bin"
export GOBIN=$GOPATH/bin

Node Hello World

The
source code for the node examples is here.
This project has several components. First, there are the .js file.

#!/usr/bin/env node
"use strict";

/*

Hello World Commandline Tool

node index.js --phrase "hello world" --count 10

hello world hello world hello world

*/

const program = require("commander");
program
  .version("0.0.1")
  .option("-p, --phrase [value]", "phrase")
  .option("-c, --count <n>", "Number of Times To Repeat Phrase", parseInt)
  .parse(process.argv);

/**
 * Multiplies string with additional space.
 * @param {string} phrase The phrase.
 * @param {number} count The number of times to repeat
 * @returns {string} The multiplied string
 */
function phraseGenerator(phrase, count) {
  return phrase.concat(" ").repeat(count);
}

// Check to see both options are used
if (
  typeof program.phrase === "undefined" ||
  typeof program.count === "undefined"
) {
  console.error("ERROR! --phrase and --count options required");
  program.help();
  process.exit(1);
}

// Print Phrase To Standard Out
console.log(phraseGenerator(
  program.phrase,
  program.count,
));

Next there is a package.json file.

{
  "name": "nodecli",
  "version": "1.0.0",
  "description": "nodecli",
  "main": "index.js",
  "dependencies": {
    "commander": "^2.10.0"
  },
  "devDependencies": {
    "eslint": "^4.1.1",
    "eslint-config-defaults": "^9.0.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/noahgift/nodecli.git"
  },
  "keywords": [
    "cli"
  ],
  "author": "Noah Gift",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/noahgift/nodecli/issues"
  },
  "homepage": "https://github.com/noahgift/nodecli#readme"
}

To run the example, you would do the following.

Steps to run:

npm install ./hello-world --phrase "hello world" --count 3

The output should be:

hello world hello world hello world

Multi-paradigm Node.js

Getting Started

This application
source code is here.

To build this project, you need to:

npm install

To talk to the blockchain network, please run ./startFabric.sh. Then
./query-cli.js. Further information and background of this forked version can be
found here

Features

  • Color Output
  • JSON Formatting
  • Async Network Operations
  • Blockchain Integration

Screenshots

  • Screenshot No Options:
    Output
  • Screenshot One Option:
    Output

The source is below. You can see how a more sophisticated node tool integrates
with both blockchain, and colored output works.

#!/usr/bin/env node
"use strict";

/*

Hyperledger Query Commandline Tool

./query-cli.js
__dirname = path.resolve();
*/
const Hfc = require("fabric-client"),
  path = require("path"),
  chalk = require("chalk"),
  prettyjson = require("prettyjson"),
  program = require("commander"),
  options = {
    walletPath: path.join(__dirname, "./network/creds"),
    userId: "PeerAdmin",
    channelId: "mychannel",
    chaincodeId: "fabcar",
    networkUrl: "grpc://localhost:7051",
  };
let channel = {},
  transactionId = null,
  client = null,
  jsonResult = null;
program
  .version("0.0.1")
  .option("-c, --car [value]", "car to query")
  .parse(process.argv);

/**
 * Queries Blockchain
 * @param {string} chaincodeFunc The chaincode function to query
 * @param {string} car The individual car to query
 * @returns {string} nothing
 */
function queryHelper(chaincodeFunc = "queryAllCars", car = "") {
  Promise.resolve().then(() => {
    console.log(chalk.red("Create a client and set the wallet location"));
    client = new Hfc();

    return Hfc.newDefaultKeyValueStore({ path: options.walletPath });
  })
    .then((wallet) => {
      console.log(
        chalk.red(
          "Set wallet path, and associate user ",
          options.userId,
          " with application",
        ),
      );
      client.setStateStore(wallet);

      return client.getUserContext(options.userId, true);
    })
    .then((user) => {
      console.log(
        chalk.red(
          "Check user is enrolled, and set a query URL in the network",
        ),
      );
      if (typeof user === "undefined" || user.isEnrolled() === false) {
        console.error("User not defined, or not enrolled - error");
      }
      channel = client.newChannel(options.channelId);
      channel.addPeer(client.newPeer(options.networkUrl));
    })
    .then(() => {
      console.log(chalk.red("Make query"));
      transactionId = client.newTransactionID();
      console.log(
        chalk.red("Assigning transaction_id: "),
        chalk.green(transactionId._transaction_id),
      );

      // The queryCar - requires 1 argument, ex: args: ['CAR4'],
      // The queryAllCars - requires no arguments , ex: args: [''],
      const request = {
        chaincodeId: options.chaincodeId,
        txId: transactionId,
        fcn: chaincodeFunc,
        args: [car],
      };

      return channel.queryByChaincode(request);
    })
    .then((queryResponses) => {
      console.log(chalk.red("returned from query"));
      if (typeof queryResponses.length === "undefined") {
        console.log("No payloads were returned from query");
      } else {
        console.log(
          chalk.bgBlue("Query result count = ", queryResponses.length),
        );
      }
      if (queryResponses[0] instanceof Error) {
        console.error("error from query = ", queryResponses[0]);
      }
      jsonResult = JSON.parse(queryResponses[0].toString());
      console.log(prettyjson.render(jsonResult, {
        keysColor: "yellow",
        dashColor: "blue",
        stringColor: "white",
      }));
    })
    .catch((err) => {
      console.error("Caught Error", err);
    });
}
// Run The Command line Tool
if (typeof program.car === "undefined") {
  queryHelper("queryAllCars");
} else {
  queryHelper("queryCar", program.car);
}

Python Hello World

Recommended environment

Python 3.6.1

Running

Steps to Run:

Install packages:

make install

Activate Virtual Env:

source ~/.hello-world-py-cli/bin/activate

Run Tool:

./hello-world.py --phrase "hello world" --count 3

The output should be:

hello world hello world hello world

The `Makefile looks like:

install:
      mkdir -p ~/.hello-world-py-cli &&\
      python3 -m venv ~/.hello-world-py-cli &&\
      pip install -r requirements.txt


source-cmd:
    echo "Virtualenv source command" 
    #source ~/.hello-world-py-cli/bin/activate

lint:
    pylint hello-world.py

The requirements.txt file looks like:

click
pylint

Finally, the python code is as follows.

#!/usr/bin/env python
import click

@click.version_option("0.1")
@click.group()
def cli():
    """Hello World"""

@cli.command("hello")
@click.option("--phrase", help="phrase to print")
@click.option("--count", help="Number of times to repeat phrase", type=int)
def hello(phrase, count):
    """Hello World Command-line tool"""

    while count:
        count -= 1
        click.echo(phrase)


if __name__ == '__main__':
    cli()

A full example of lint and run:

    (.hello-world-py-cli) $  hello-world git:(master) $ ./hello-world.py hello\
        --phrase "hello world" --count 3
    hello world
    hello world
    hello world
    (.hello-world-py-cli) $  hello-world git:(master) $ make lint
    pylint hello-world.py

    -------------------------------------------------------------------
    Your code has been rated at 10.00/10 (previous run: 7.50/10, +2.50)

You can find the latest up to date examples in the Github Repo
https://github.com/noahgift/cli-rosetta.

## Recommended Courses

🎓 Continue Your Learning Journey

Python Command Line Mastery

Master advanced Click patterns, testing strategies, and deployment techniques for production CLI tools.

  • Advanced Click decorators and context handling
  • Comprehensive CLI testing with pytest
  • Packaging and distribution best practices
  • Performance optimization for large-scale tools
View Course →

DevOps with Python

Learn to build automation tools, deployment scripts, and infrastructure management CLIs with Python.

  • Infrastructure automation with Python
  • Building deployment and monitoring tools
  • Integration with cloud platforms (AWS, GCP, Azure)
  • Real-world DevOps CLI examples
View Course →

Python Testing and Quality Assurance

Ensure your CLI tools are robust and reliable with comprehensive testing strategies.

  • Unit testing Click applications
  • Integration testing for CLI tools
  • Mocking external dependencies
  • Continuous integration for CLI projects
View Course →
### Chapter-Specific Resources - **CLI Security Best Practices**: Handle sensitive data and secure your tools - **Logging and Monitoring**: Add observability to your CLI applications - **Production-Ready CLIs**: Scale your tools for enterprise use

📝 Test Your Knowledge: System Integration

Take this quiz to reinforce what you've learned in this chapter.