<!-- .slide: data-background="#fff" -->
# Build a CLI Program
### Speaker: @tigercosmos
---
<!-- .slide: data-background="#fff" -->
## What is CLI?
command-line interface, aka none-GUI
https://github.com/agarrharr/awesome-cli-apps
----
<!-- .slide: data-background="#fff" -->
### exa
A modern version of ‘ls’. Written in Rust.
https://github.com/ogham/exa
![](https://raw.githubusercontent.com/ogham/exa/master/screenshots.png)
----
<!-- .slide: data-background="#fff" -->
## Meow
Create meow image with weather infomation
https://github.com/weather-bot/meow
----
![](https://i.imgur.com/zWk2hNJ.png)
---
<!-- .slide: data-background="#fff" -->
## A CLI program
```bash
$ ./program_name [arguments] [flags] [options]
```
----
<!-- .slide: data-background="#fff" -->
```bash
$ ./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.
```
---
<!-- .slide: data-background="#fff" -->
## Build a CLI program from scratch
Take `Meow` for example
![](https://i.imgur.com/GgfeS90.jpg)
---
<!-- .slide: data-background="#fff" -->
### Create project
```bash
$ cargo new meow
$ cd meow
```
---
<!-- .slide: data-background="#fff" -->
## Arguments
```rust
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}
```
```bash
$./foo a1 a2 a3
a1
a2
a3
```
----
<!-- .slide: data-background="#fff" -->
inconvenient to use
- arguments might have default value
- flags cannot exchange positions
- what if you need options
- `arg1` binds `arg2`
```bash
$ ./foo -g -e a1 a3 a4
$ ./foo a1 -e -1 --path=~/test/123
```
----
<!-- .slide: data-background="#fff" -->
### Clap
A full featured, fast Command Line Argument Parser for Rust
https://github.com/clap-rs/clap
----
<!-- .slide: data-background="#fff" -->
```yml
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
```
----
<!-- .slide: data-background="#fff" -->
```rust
#[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...
}
```
----
<!-- .slide: data-background="#fff" -->
```bash
$ 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
```
---
<!-- .slide: data-background="#fff" -->
## Configuration
```env
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
----
<!-- .slide: data-background="#fff" -->
### dotenv_codegen
https://crates.io/crates/dotenv_codegen
the crate reads `.env`
```rust
fn main() {
println!("{}", dotenv!("PORT"));
}
```
---
<!-- .slide: data-background="#fff" -->
## Environment Variables
`std::env`
```rust
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
![](https://i.imgur.com/tE6KjSn.png)
----
<!-- .slide: data-background="#fff" -->
### panic
```rust
panic!("this is panic");
```
- break the program
- exit without code
- better use in script
----
<!-- .slide: data-background="#fff" -->
### Result
```rust
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.
----
<!-- .slide: data-background="#fff" -->
### Error Message
```rust
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),
}
}
}
```
```rust
Err(e) => println!("{}", e)
// `XXX` is the error
```
----
<!-- .slide: data-background="#fff" -->
### Standard error
```bash
$ cargo run > output.txt
```
we don't want to write error messages in logs.
```rust
eprintln!(); // just like `println!()`
```
----
<!-- .slide: data-background="#fff" -->
### Exit Code
`std::process`
```rust
process::exit(1);
```
none-zero code lets other programs know it failed
---
## WxKitty
![](https://i.imgur.com/p93EKgB.png)
----
![](https://raw.githubusercontent.com/weather-bot/weather-bot/master/img/qrcode.png)
{"metaMigratedAt":"2023-06-14T18:56:20.329Z","metaMigratedFrom":"Content","title":"Build a CLI Program","breaks":"true","contributors":"[{\"id\":\"b4ba298e-5cbb-419b-a0c9-4f2e4612e8bf\",\"add\":21325,\"del\":14851}]"}