Try   HackMD

Build a CLI Program

Speaker: @tigercosmos


What is CLI?

command-line interface, aka none-GUI

https://github.com/agarrharr/awesome-cli-apps


exa

A modern version of ‘ls’. Written in Rust.

https://github.com/ogham/exa

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


Meow

Create meow image with weather infomation
https://github.com/weather-bot/meow


Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


A CLI program

$ ./program_name [arguments] [flags] [options]

$ ./meow -h
meow 0.0.1
tigercosmos <phy.tiger@gmail.com>
Create meow image with weather infomation

USAGE:
    meow [FLAGS] [OPTIONS] <mode> <image> <info_json>

FLAGS:
    -h, --help       Prints help information
    -l, --lang-en    image in English
    -V, --version    Prints version information

OPTIONS:
    -o, --output <output>    path of output image [default: out.jpg]

ARGS:
    <mode>         What kind of the image. [possible values: corner-mode, bottom-mode]
    <image>        Input a kitty image. 800x800 better. If not, it would be cropped.
    <info_json>    Weather infomation json as string. Including title, time, location, temp, humd, overview,
                   overview2.

Build a CLI program from scratch

Take Meow for example

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


Create project

$ cargo new meow
$ cd meow

Arguments

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);
}
$./foo a1 a2 a3
a1
a2
a3

inconvenient to use

  • arguments might have default value
  • flags cannot exchange positions
  • what if you need options
  • arg1 binds arg2
$ ./foo -g -e a1 a3 a4
$ ./foo a1 -e -1 --path=~/test/123

Clap

A full featured, fast Command Line Argument Parser for Rust

https://github.com/clap-rs/clap


name: myapp
version: "1.0"
author: Kevin K. <kbknapp@gmail.com>
about: Does awesome things
args:
    - config:
        short: c
        long: config
        value_name: FILE
        help: Sets a custom config file
        takes_value: true
    - INPUT:
        help: Sets the input file to use
        required: true
        index: 1
    - verbose:
        short: v
        multiple: true
        help: Sets the level of verbosity
subcommands:
    - test:
        about: controls testing features
        version: "1.3"
        author: Someone E. <someone_else@other.com>
        args:
            - debug:
                short: d
                help: print debug information

#[macro_use]
extern crate clap;
use clap::App;

fn main() {
    // The YAML file is found relative to the current file, similar to how modules are found
    let yaml = load_yaml!("cli.yml");
    let matches = App::from_yaml(yaml).get_matches();

    // Same as previous examples...
}

$ myprog --help
My Super Program 1.0
Kevin K. <kbknapp@gmail.com>
Does awesome things

USAGE:
    MyApp [FLAGS] [OPTIONS] <INPUT> [SUBCOMMAND]

FLAGS:
    -h, --help       Prints help information
    -v               Sets the level of verbosity
    -V, --version    Prints version information

OPTIONS:
    -c, --config <FILE>    Sets a custom config file

ARGS:
    INPUT    The input file to use

SUBCOMMANDS:
    help    Prints this message or the help of the given subcommand(s)
    test    Controls testing features

Configuration

PORT = 8000
PATH = "home/foo/bar"
MODE = "happy mode"
ZONE = 8
AREA = "Taipei"

you could write by hands

  • read file .env
  • split \n
  • split = and add to HashMap

or use a crate


dotenv_codegen

https://crates.io/crates/dotenv_codegen

the crate reads .env

fn main() {
  println!("{}", dotenv!("PORT"));
}

Environment Variables

std::env

use std::env;

let key = "HOME";
match env::var_os(key) {
    Some(val) => println!("{}: {:?}", key, val),
    None => println!("{} is not defined in the environment.", key)
}

Error handling


panic

panic!("this is panic");
  • break the program
  • exit without code
  • better use in script

Result

enum MyErr {
    Reason1,
    Reason2,
}
fn foo() -> Result<(), MyErr> {
    match bar {
        Some(_)=>{}
        Nono => Err(MyErr::Reason1)
    }
}
fn hoo() {
    match foo() {
        Ok(_) => reply(),
        Err(e) => show_error(e) 
        // `e` not work yet
        // we need `fmt` to tranlate to the message
    }
}

Result pass the error without crash.


Error Message

enum MyErr {
    Reason1(String),
    Reason2(String, u32),
}
impl fmt::Display for DrawError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MyErr::Reason1(ref s) => 
                write!(f, "`{}` is the error", s),
            MyErr::Reason2(ref s, ref num) => 
                write!(f, "`{}` and `{}` are error", s, num),
        }
    }
}
Err(e) => println!("{}", e)
// `XXX` is the error

Standard error

$ cargo run > output.txt

we don't want to write error messages in logs.

eprintln!(); // just like `println!()`

Exit Code

std::process

process::exit(1);

none-zero code lets other programs know it failed


WxKitty