Rust에서 첫 번째 명령줄 애플리케이션 만들기

25388 단어 beginnersrust
프로그래머가 되어 터미널 작업을 많이 하게 될 것입니다. 대부분의 경우 명령줄 응용 프로그램을 사용하여 터미널에서 작업을 수행합니다.

그러나 때로는 터미널에서 작업할 때 달성하고 싶은 일부 워크플로가 있지만 사용하고 싶은 애플리케이션이 없습니다.

따라서 이 경우 실제로 자신만의 명령줄 응용 프로그램을 작성할 수 있습니다. 저는 터미널에서 반복되는 작업을 자동화하기 위해 많은 명령줄 응용 프로그램을 작성합니다.

저는 다양한 프로그래밍 언어로 명령줄 응용 프로그램을 만들려고 노력해 왔으며 Rust가 저를 행복하게 만드는 것임을 알게 되었습니다. 사용하기 쉽고 터미널에서 일부 작업을 수행하는 데 사용할 수 있는 꽤 많은 라이브러리가 있습니다.

명령줄 응용 프로그램을 작성할 때 프로젝트를 구성하는 방법에는 여러 가지가 있습니다. 제가 가장 좋아하는 방법은 cargo입니다.

프로젝트 구조



명령줄 응용 프로그램을 작성할 때 처리해야 하는 세 가지 지루한 작업이 있습니다.
  • arg 입력 구문 분석 중
  • 명령 플래그 확인
  • 기능 처리

  • 따라서 이 문제를 해결하기 위해 다음과 같은 프로젝트 구조를 구성해 보겠습니다.

    └── src
        ├── cli
        │   ├── mod.rs
        │   └── parser.rs
        ├── commands
        │   ├── mod.rs
        │   └── new.rs
        └── main.rs
    


    이 프로젝트 구조에서는 두 개의 크레이트clapanyhow를 사용하며 이 두 라이브러리는 인수 파서 및 오류 처리를 처리합니다.

    코딩하자



    먼저 명령줄 응용 프로그램에 대한 구문 분석기를 만들어 보겠습니다. 이 코드에서는 응용 프로그램의 명령과 하위 명령을 등록합니다.

    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}", "\t--help")
        );
    }
    
    fn get_template() -> &'static str {
        "\
    rust-clap-cli v0.1
    USAGE:
        {usage}
    OPTIONS:
    {options}
    Some common rust-clap-cli commands are (see all commands with --list):
        help           Show help
    See 'rust-clap-cli help <command>' for more information on a specific command.\n"
    }
    


    다음: 기본 오류 처리 및 박수 앱 설정을 처리하는 cli 모듈을 만듭니다.

    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)
        }
    }
    


    하위 명령



    이제 우리 앱의 하위 명령을 구성해 보겠습니다. 이것을 우리 애플리케이션에 대한 경로와 같이 생각할 수 있습니다.

    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)
    }
    


    이렇게 새로운 하위 명령을 쉽게 추가할 수 있습니다.

    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.\n")
    }
    
    pub fn exec(_: &mut Config, _: &ArgMatches) -> CliResult {
        println!("Hei thanks to create new project");
        Ok(())
    }
    


    모두 결합



    이제 모든 코드를 결합하고 명령줄 애플리케이션의 기본 진입점에서 호출해 보겠습니다.

    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(())
    }
    


    이제 우리는 이와 같이 애플리케이션을 테스트할 수 있습니다.

    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
    


    해당 메시지를 모두 설정할 준비가 되었으면 명령줄 앱 만들기를 시작할 수 있습니다.

    이 자습서의 전체 소스 코드는 github here에서 사용할 수 있습니다.

    좋은 웹페이지 즐겨찾기