Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
June 18, 2022 04:17 pm GMT

Create your First Command-line Application in Rust

Becoming a Programmer to be sure we will working with terminal a lot. Most of the time we will use command line application to do our work in terminal.

But sometime there are some workflow that we want to achieve when working on terminal but there is no application that you like to use it.

So for that case you actually can write your own command line application. As for me i write a lot of command line application to automate some repeated work on terminal.

I have been trying to create command line application in various programming language, and find that Rust is the one that make me happy when I need to write some command line application.

There are multiple way how we can structure our project when writing command line application, the one that I like the most is following cargo.

Project structure

When you are writing command line application, there are three boring task you need to handle.

  1. Parsing arg input
  2. Validate command flag
  3. Handle the functionality

So to fix this issue let's construct our project structure like this.

 src     cli        mod.rs        parser.rs     commands        mod.rs        new.rs     main.rs

With this project structure we will use two crates clap and anyhow, this two library will handle argument parser and error handling.

Let's Code

First let's create a parser to our command line application, in this code we will register command and subcommand of our application.

use crate::commands;use crate::cli::*;pub fn parse() -> App {    let usage = "rust-clap-cli [OPTIONS] [SUBCOMMAND]";    App::new("rust-clap-cli")        .allow_external_subcommands(true)        .setting(AppSettings::DeriveDisplayOrder)        .disable_colored_help(false)        .override_usage(usage)        .help_template(get_template())        .arg(flag("version", "Print version info and exit").short('V'))        .arg(flag("help", "List command"))        .subcommands(commands::builtin())}pub fn print_help() {    println!("{}", get_template()        .replace("{usage}", "rust-clap-cli [OPTIONS] [SUBCOMMAND]")        .replace("{options}", "--help")    );}fn get_template() -> &'static str {    "\rust-clap-cli v0.1USAGE:    {usage}OPTIONS:{options}Some common rust-clap-cli commands are (see all commands with --list):    help           Show helpSee 'rust-clap-cli help <command>' for more information on a specific command.
"}

Next: create cli module to handle some basic error handling and clap app setup.

mod parser;pub use clap::{value_parser, AppSettings, Arg, ArgAction, ArgMatches};pub type App = clap::Command<'static>;pub use parser::parse;pub use parser::print_help;#[derive(Debug)]pub struct Config {}pub fn subcommand(name: &'static str) -> App {    App::new(name)        .dont_collapse_args_in_usage(true)        .setting(AppSettings::DeriveDisplayOrder)}pub trait AppExt: Sized {    fn _arg(self, arg: Arg<'static>) -> Self;    fn arg_new_opts(self) -> Self {        self    }    fn arg_quiet(self) -> Self {        self._arg(flag("quiet", "Do not print log messages").short('q'))    }}impl AppExt for App {    fn _arg(self, arg: Arg<'static>) -> Self {        self.arg(arg)    }}pub fn flag(name: &'static str, help: &'static str) -> Arg<'static> {    Arg::new(name)        .long(name)        .help(help)        .action(ArgAction::SetTrue)}pub fn opt(name: &'static str, help: &'static str) -> Arg<'static> {    Arg::new(name).long(name).help(help)}pub type CliResult = Result<(), CliError>;#[derive(Debug)]pub struct CliError {    pub error: Option<anyhow::Error>,    pub exit_code: i32,}impl CliError {    pub fn new(error: anyhow::Error, code: i32) -> CliError {        CliError {            error: Some(error),            exit_code: code,        }    }}impl From<anyhow::Error> for CliError {    fn from(err: anyhow::Error) -> CliError {        CliError::new(err, 101)    }}impl From<clap::Error> for CliError {    fn from(err: clap::Error) -> CliError {        let code = if err.use_stderr() { 1 } else { 0 };        CliError::new(err.into(), code)    }}impl From<std::io::Error> for CliError {    fn from(err: std::io::Error) -> CliError {        CliError::new(err.into(), 1)    }}

Sub-commands

Now let's organize our sub-command of our app, you can think of this like route to our application.

use super::cli::*;pub mod new;pub fn builtin() -> Vec<App> {    vec![        new::cli()    ]}pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches) -> CliResult> {    let f = match cmd {        "new" => new::exec,        _ => return None,    };    Some(f)}

Thank we can easily just add new subcommand like this.

use crate::cli::*;pub fn cli() -> App {    subcommand("new")        .about("Create a new rust-clap-cli  project at <path>")        .arg_quiet()        .arg(Arg::new("path").required(true))        .arg(opt("registry", "Registry to use").value_name("REGISTRY"))        .arg_new_opts()        .after_help("Run `rust-clap-cli help new` for more detailed information.
")}pub fn exec(_: &mut Config, _: &ArgMatches) -> CliResult { println!("Hei thanks to create new project"); Ok(())}

Combine All

Now let's combine all of our code and call it from main entry point of our command line application.

mod cli;mod commands;use cli::*;fn main() -> CliResult {    let mut config = Config{};    let args = match cli::parse().try_get_matches() {        Ok(args) => args,        Err(e) => {            return Err(e.into());        }    };    if let Some((cmd, args)) = args.subcommand() {        if let Some(cm) = commands::builtin_exec(cmd) {            let _ = cm(&mut config, args);        }    } else {        cli::print_help();    }    Ok(())}

Now we can test our application like this.

cargo run -- new work# Result...    Finished dev [unoptimized + debuginfo] target(s) in 0.03s     Running `target/debug/rust-clap-cli new work`Hei thanks to create new project

And if you got that message all ready to setup and you can start to create your command line app.

Full source code of this tutorial is available on github here.


Original Link: https://dev.to/ahmadrosid/create-your-first-command-line-application-in-rust-7cp

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To