Chapter10 Command Line Rosetta

If you find this content useful, consider buying this book:

If you enjoyed this book considering buying a copy

Chapter 10: Command-line Rosetta Stone #

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 #

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.