Coder Social home page Coder Social logo

cli_rust's Introduction

CLI_Rust

Rust Command line applications Walkthrough

Resource Git Repo

Rust Modules for CLI :

Project Setup :

  • We can use cargo new grrs command to setup new project.

Parsing Command-line arguments:

  • A typical invocation of our code tool will look like this:
$ grrs sahil names.txt
  • we expect our program to look in names.txt and print out the lines that contains sahil.
  • The text after the name of program is often called the command-lien arguments or command-line flags (specially when they look like --this).

Getting the arguments:

  • The standard library contains the function std::env::args() that gives you an iterator of the given arguments. The first entry(at index 0) will be the name your program was called as (eg grrs), the one that follow are what the user wrote afterwords.

  • Getting the raw arguments this way is quit easy:

use std::env::args;

fn main() {
    let pattern = std::env::args().nth(1).expect("no pattern given");
    let path = std::env::args().nth(2).expect("no path given");

    println!("pattern: {:?}, path: {:?}", pattern, path);

}
  • output :
sahilwep~$ cargo run -- some-pattern some-files
   Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/grrs some-pattern some-files`
pattern: "some-pattern", path: "some-files"

CLI arguments as data type

  • Instead of thinking about them as bunch of text, it often pays off to think to CLI arguments as a custom data type that represent the inputs to your program.

  • In our program grrs we have two arguments, first is pattern and then path.

  • While getting input from user:

    • The first argument is expected to be string.
    • The second argument is expected to be path.
  • We can structure programs around the data they handle, so this way of looking at CLI arguments fits very well.

struct CLI {
  pattern: String,
  path: std::path::PathBuf,
}
  • NOTE : PathBuf is like a String but for the system path that work cross-platform.
struct CLI {
    pattern: String,
    path: std::path::PathBuf,
}

fn main() {
    let pattern = std::env::args().nth(1).expect("no pattern given");
    let path = std::env::args().nth(2).expect("no path given");

    let args = CLI {
        pattern: pattern,
        path: std::path::PathBuf::from(path),
    };

    println!("pattern: {:?}, path: {:?}", args.pattern, args.path);
}
  • This works, but not very convenient. We can't deal with the requirement to support --pattern="foo" or --pattern "foo" or --help.

Parsing CLI arguments with Clap:

  • We can use one of the many available library to parse our CLI arguments. One of the popular is clap. It has all the functionality including support of sub-commands, shell completions, and great help message.

  • For importing clap we need to add clap = {version = "4.0", features = ["derive"]} to the dependencies section of our Cargo.toml file.

  • Now, we can write use clap::Parser in our code, and #[derive(parser)] right above our struct CLI

use clap::Parser;

// Search for a pattern in a file and display the lines that contains it.
struct CLI {
    // pattern to look for
    pattern: String,
    // The path to the file to read.
    path: std::path::PathBuf,
}
  • Note : There are a lot custom attributes you can add to field. For example, to say you want to use this field for the argument after -o or --output, you'd add #[arg(short = 'o', long = "output")]. clap Documentation

  • Right below the CLI struct our template contains its main function. WHen the program starts, it will call this function.

fn main(){
  let args = Cli::parse();

  println!("pattern: {:?}, path: {:?}", args.pattern, args.path);
}
  • This will try to parse the arguments into our CLI struct.

  • But what if that fails? That's the beauty of this approach: Clap knows which field to expect, and what their expected format is. It can automatically generate a nice --help message, as well as give some great errors to suggest you pass --output when you wrote --putput.

  • NOTE : The parse method is meant to be used in your main function. When it fails, it will print out an error or help message and immediately exit the program. Don't user it in other places!.

  • Our code will look something like this :

use clap::Parser;

// Search for a pattern in a file and display the lines that contains it.
#[derive(Parser)]
struct CLI {
    // pattern to look for
    pattern: String,
    // The path to the file to read.
    path: std::path::PathBuf,
}

fn main() {
    let args = CLI::parse();

    println!("pattern: {:?}, path: {:?}", args.pattern, args.path);
}
  • Output :
sahilwep~$ cargo run -- pattern-some path-some
   Compiling grrs v0.1.0 (/Users/sahilwep/Developer/Development/Rust/CLI_Rust/grrs)
    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/grrs pattern-some path-some`
pattern: "pattern-some", path: "path-some"

First implementation of grrs

  • We can take argument as an input, but we don't know how to open the file.
  • We can open any file with :
let content = std::fs:::read_to_string(&args.path).expect("could not read the file");
  • NOTE : .expect method here is a shortcut function that will make the program exit immediately when the value (in this case the input file) could not be read. It's not very pretty, and the next chapter on Nicer error reporting we will look at how to improve this.

  • Now, let's iterate over the lines and print each one that contains our pattern:

for line in content.lines() {
  if line.contents(&args.pattern) {
    println!("{}", line);
  }
}
  • Our code will look like this:
use clap::Parser;

// Search for a pattern in a file and display the lines that contains it.
#[derive(Parser)]
struct CLI {
    // pattern to look for
    pattern: String,
    // The path to the file to read.
    path: std::path::PathBuf,
}

fn main() {
    let args = CLI::parse();
    let content = std::fs::read_to_string(&args.path).expect("Could not read file");

    for line in content.lines() {
        if line.contains(&args.pattern) {
            println!("{}", line);
        }
    }
}
  • Output :
sahilwep~$ cargo run -- name file.txt
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
     Running `target/debug/grrs name file.txt`
 sahilwep is my name

Nice Error reporting :

  • We all can do nothing but accept the fact that errors will occur. And in contrast to many other language. It's very hard not to notice and deal with his reality when using Rust: As it doesn't have exceptions, all possible error states are often encoded in the return types of function.

Results

  • A function like read_to_string doesn't return a string. Instead, it returns a Result that contains either a String or an error of some type(in case std::io::Error).
  • How do you know which it is? Since Result is an enum, you can use match to check which variant it is:
let result = std::fs::read_to_string("text.txt");
match result {
  Ok(content) => {println!("File content: {}", content);}
  Err(error) => {println!("Oh noes: {}", error);}
}

Unwrapping

  • Now, we were able to access the content of the file, but we can't really do anything with it after the match block. For this, we'll need to somehow deal with the error case. The challenge is that all arms of a match block need to return something of the same type. But there's a neat trick to get around that:
let result = std::fs::read_to_string("test.txt");
let content = match result {
  Ok(content) => {content},
  Err(error) => { panic!("can't deal with {}, just exit here", error);}
};
println!("file content: {}", content);
  • We can use the String in content after the match block. If result were an error, the String wouldn't exit. But since the program would exit before it ever reached a point where we use content, it's fine.

  • This may seem drastic, but it's very convenient. If your program needs to read that file and can't do anything if the file doesn't exist, existing is valid strategy. There's even a shortcut method on Results, called unwrap:

let content = std::fs::read_to_string("test.txt").unwrap();

No need to panic

cli_rust's People

Contributors

sahilwep avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.