Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

CLI (Command Line Interface) — Интерфейс командной строки

Библиотеки для работы с терминалом:

crate ratatui, ratatui.rs — это TUI библиотека Rust для приготовления вкусных TUI (терминальных пользовательских интерфейсов). Это легкая библиотека, которая предоставляет набор виджетов и утилит для создания простых или сложных Rust TUI

crate termcolor - Цветной вывод . Кроссплатформенный вывод цвета терминала

crate indicatif - Индикаторы прогресса. Индикаторы выполнения и счетчики

crate crossterm - Низкоуровневый кроссплатформенный рендеринг терминала и обработка событий

crate inquire - Интерактивные подсказки. Запрос подтверждения, выбора, ввода текста и т. д.

crates#section-cli-tools-subsection-rendering

Параметры функции: Параметры - это переменные, которые определяются в сигнатуре (заголовке) функции и используются для передачи значений в функцию.

fn greet(name){...}

Аргументы функции: Аргументы - это фактические значения, которые передаются в функцию при ее вызове.

let arg = "Hello World!";
greet(arg);

При работе с именами переменных среды и аргументами командной строки в формате операционной системы пользуйтесь типами OsStr и OsString

Обзор руководства по интерфейсам командной строки (Command Line Interface Guidelines, CLIG)

clig.dev

Обзор руководства по интерфейсам командной строки (Command Line Interface Guidelines, CLIG) Руководство по интерфейсам командной строки (CLIG) — это исчерпывающий ресурс для разработки эффективных интерфейсов командной строки (CLI). Вот основные идеи и рекомендации из этого руководства:

1. Ориентированность на пользовательский опыт (UX): Приоритизируйте потребности пользователя, обеспечивая интуитивность и простоту использования CLI. Предоставляйте полезные и информативные сообщения об ошибках. Обеспечивайте понятную обратную связь и индикаторы выполнения.

2. Последовательность: Поддерживайте консистентность в синтаксисе команд, опциях и поведении. Следуйте установленным соглашениям и стандартам, чтобы уменьшить кривую обучения.

3. Обнаруживаемость: Включайте встроенную справку и документацию, доступные через команды типа --help или -h. Используйте описательные названия команд и опций.

4. Простота и минимализм: Держите CLI простым и избегайте ненужной сложности. Проектируйте команды так, чтобы они хорошо выполняли одну задачу (философия Unix). Предоставляйте разумные настройки по умолчанию и минимизируйте необходимый ввод.

5. Ввод и вывод: Поддерживайте каналы (piping) и перенаправление для интеграции с другими инструментами. Используйте стандартные потоки ввода, вывода и ошибок (stdin, stdout, stderr) соответственно. Обеспечивайте форматы вывода, удобные для машинного чтения (например, JSON), для легкого парсинга.

6. Безопасность и конфиденциальность: Обрабатывайте конфиденциальные данные безопасно. Избегайте раскрытия конфиденциальной информации в сообщениях об ошибках или логах.

7. Обработка ошибок: Завершайте работу с соответствующими кодами статуса, чтобы указать на успех или неудачу. Предоставляйте четкие и понятные сообщения об ошибках.

8. Производительность: Обеспечивайте отзывчивость и хорошую производительность CLI, даже при больших объемах ввода или вывода. Оптимизируйте для общих сценариев использования и рабочих процессов.

9. Расширяемость и модульность: Проектируйте CLI таким образом, чтобы его можно было легко расширять с помощью плагинов или модулей. Позволяйте пользователям настраивать и расширять функциональность без изменения основного кода.

10. Документация: Предоставляйте исчерпывающую и доступную документацию. Включайте примеры и сценарии использования для иллюстрации общих задач.

Практические советы

  • Структура команд: Используйте четкую и логичную структуру для команд и подкоманд.
  • Парсинг опций и аргументов: Используйте библиотеки или фреймворки, которые упрощают парсинг опций и аргументов.
  • Интерактивные режимы: Рассмотрите возможность предложения интерактивных режимов для сложных задач.
  • Обратная совместимость: Сохраняйте обратную совместимость, чтобы не ломать пользовательские рабочие процессы.
  • Тестирование: Тщательно тестируйте CLI, чтобы убедиться, что он работает как ожидается в различных средах.

Заключение Следуя этим рекомендациям, разработчики могут создавать CLI, которые являются удобными для пользователя, надежными и эффективными инструментами, повышая их продуктивность и общий опыт использования.

std::env

std::env

Переменные среды (environment variables)

environment-variables-cargo-sets-for-build-scripts


fn main(){
    use std::env;
    let out_dir = env::var("OUT_DIR").unwrap();
}
  • args - Возвращает аргументы, с которыми была запущена эта программа (обычно передается через командную строку).
  • args_os - Возвращает аргументы, с которыми была запущена эта программа (обычно передается через командную строку).
  • current_dir - Возвращает текущий рабочий каталог как PathBuf.
  • current_exe - Возвращает полный путь к файловой системе текущего исполняемого исполняемого файла.
  • home_dir - Возвращает путь к домашнему каталогу текущего пользователя, если он известен.
  • join_paths - Соединяет коллекцию путей соответствующим образом для переменной среды PATH.
  • remove_var - Удаляет переменную среды из среды текущего процесса.
  • set_current_dir - Изменяет текущий рабочий каталог на указанный путь.
  • set_var - Устанавливает переменную окружения k в значение v для текущего процесса.
  • split_paths - Вход парсов в соответствии с соглашениями платформы для переменной среды PATH.
  • temp_dir - Возвращает путь к временному каталогу.
  • var - Извлекает ключ переменной среды из текущего процесса.
  • var_os - Выбирает ключ переменной среды из текущего процесса, возвращая None, если переменная не установлена.
  • vars - Возвращает итератор пар (переменных, значений) строк для всех переменных среды текущего процесса.
use std::env;
// $env:CASE_INSENSITIVE=1 
// cargo run to poem.txt

// CASE_INSENSITIVE=1 cargo run to poem.txt
fn main() -> Result<(), &'static str> {
    let args: Vec<String> = std::env::args().collect(); 

    // cargo run query_value filename_value
    let query = args[1].clone();
    let filename = args[2].clone();
    println!("query={:?}",query);
    println!("filename={:?}",filename);

    let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
    println!("env::var CASE_INSENSITIVE ={:?}",! case_sensitive);

    // env::set_var
    let key = "CASE_INSENSITIVE";
    env::set_var(key, "1");
    assert_eq!(env::var(key), Ok("1".to_string()));

    // env::remove_var
    env::remove_var(key);
    assert!(env::var(key).is_err());

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

fn main(){
    std::env::set_var("URL_REMOTE_SERVER",  "http://192.168.0.104:4011");
    if std::env::args().len() > 1 {
         let args: Vec<String> = std::env::args().collect(); 
         std::env::set_var("URL_REMOTE_SERVER",  args[1].clone());
    }
   let url = std::env::var("URL_REMOTE_SERVER").unwrap()
}

пример передачи параметров командной строки


// cargo run --bin parser_vtt 
pub fn main() -> Result<(), std::io::Error> {
    let args: Vec = std::env::args().collect(); 
    if args.len() < 2 {
        return Err(std::io::Error::other("file notfound"));
    }
    let from_sub = args[1].clone();

    normalize_subtitles(from_sub.as_str())?;
    Ok(())
}

fn main(){
    // All vars
    for (key, value) in env::vars_os() {
      //  println!("{:?}: {:?}", key, value);
    }
    // env::split_paths 
    let key = "PATH";
    match env::var_os(key) {
        Some(paths) => {
            for path in env::split_paths(&paths) {
                println!("'{}'", path.display());
            }
        }
        None => println!("{} is not defined in the environment.", key)
    }
    //'/home/mint/bin'
    //'/home/mint/.local/bin'
    //'/usr/local/sbin'
     
    // Возвращает путь к домашнему каталогу текущего пользователя, если он известен.
    match env::home_dir() {
        Some(path) => println!("{}", path.display()),
        None => println!("Impossible to get your home dir!"),
    }
}
use std::env;
fn test_env(){
/*<absent> если переменная не определена;
<empty> если переменная определена, но является пустой строкой;
значение переменной в других случаях.*/

// ENV_VAR_ONE=1 cargo run 
let vars:[&str;3]=["ENV_VAR_ONE","ENV_VAR_TWO","ENV_VAR_THREE"];

for var in vars.into_iter(){
    match env::var_os(var) {
        Some(value) => {
               if value.is_empty() { 
                  println!("{}: <empty>",  var);
               }else{ println!("{}: {:?}", var, value); }   
         },
        None => {println!("{}: <absent>",var);}
    }
}

process::exit(1); - Функция остановит программу немедленно и вернуть номер , который был принят в качестве кода состояния выхода.


use std::process;
fn main() {
    let args: Vec = env::args().collect();

    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        process::exit(1);
    });
}

Вызов команды для выполнения в терминале:

std::process::Command::new("sh")

Примеры работы с stdout, stdin, stderr во вкладке Base.

Module std::process

std/process

tokio/process

Модуль для работы с процессами.

Этот модуль в основном занимается порождением дочерних процессов и взаимодействием с ними, но он также обеспечивает abort и exit для завершения текущего процесса.

Обработка ввода / вывода

stdout, stdin и stderr из дочернего процесса может быть сконфигурирован путем передачи Stdio к соответствующему методу на Command

Как вызывать команду оболочки


use std::process::{Child, Command, Output, Stdio};
use std::io::Write;

fn main() -> std::io::Result<()>{
    // ffmpeg -i example1.wav -f mp3 example2.mp3
    let mut child = Command::new("ffmpeg").args(["-i","example1.wav","-f","mp3", "example3.mp3"])
        .stdin(Stdio::piped())
        .stderr(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .map_err(|_|std::io::Error::new(std::io::ErrorKind::Other,"Command error"))?;
    
    child.stdin
        .as_mut()
        .ok_or("Child process stdin has not been captured!")
        .map_err(|_|std::io::Error::new(std::io::ErrorKind::Other,"Command error"))?
        .write_all(b"something...")?;

    let output = child.wait_with_output()?;
    //println!("{:#?}",output);
 
    Ok(())
}
use std::ffi::OsStr;
use std::io::Write;
use std::process::{Child, Command, Output, Stdio};

fn spawn_child<I, S>(args: I) -> Child
where
    I: IntoIterator<Item = S>,
    S: AsRef<OsStr>,
{
    Command::new("cargo")
        .args(&["run", "-p", "step_3_1", "--"])
        .args(args)
        .stdout(Stdio::piped())
        .stdin(Stdio::piped())
        .spawn()
        .expect("Failed to run step_3_1")
}

fn get_output<I, S>(args: I) -> Output
where
    I: IntoIterator<Item = S>,
    S: AsRef<OsStr>,
{
    Command::new("cargo")
        .args(&["run", "-p", "step_3_1", "--"])
        .args(args)
        .output()
        .expect("Failed to run step_3_1")
}

fn write<'a, B: Into<&'a [u8]>>(child: &mut Child, buf: B) {
    let stdin = child.stdin.as_mut().expect("Failed to open stdin");
    stdin
        .write_all(buf.into())
        .expect("Failed to write to stdin");
}

fn get_stdout(child: Child) -> Vec<u8> {
    child
        .wait_with_output()
        .expect("Process did not end after right number was given")
        .stdout
}

Асинхронный вариант Command от Tokio

tokio/process


use tokio::process::Command;
#[tokio::main]
async fn main() -> Result<(), Box> {
    // Like above, but use `output` which returns a future instead of
    // immediately returning the `Child`.
    let output = Command::new("echo").arg("hello").arg("world").output();
    let output = output.await?;
    assert!(output.status.success());
    assert_eq!(output.stdout, b"hello world\n");
    Ok(())
}

Также можно запустить в отдельном потоке прослушивание команд консоли


fn main(){
    thread::spawn(move || loop {
        let mut cmd = String::new();
        if io::stdin().read_line(&mut cmd).is_err() {
            println!("error");
            return;
        }
        if cmd == "start\n".to_string(){ 
           ...
        }   
    });
}

std::env::args_os() - Параметры командной строки. Если ваша программа должна принимать аргументы, содержащие недопустимый Unicode, используйте std::env::args_os

std/env/fn.args_os


use std::env;
fn main(){
    for (i,argument) in env::args_os().enumerate() {
        println!("{}: {:?}",i, argument);
    }
    // 0: "target/debug/test2"
    // 1: "param"
}

Запуск:

$ cargo run --bin test2 param


fn main(){
    if env::args_os().len() >1 {
        let hundred = env::args_os().enumerate().collect::>();
        let (_,ref  value) = hundred[1];
        if let Some(n) = value.to_str(){
            println!("{}",n);
        }
    }else{...}
}

std::env::args() - Принятие аргументов командной строки.

accepting-command-line-arguments


use std::env;
fn main() {
    let args: Vec = env::args().collect();
    // Первый аргумент - это путь, по которому была вызвана программа.
    println!("My path is {}.", args[0]);
    // Остальные аргументы - это переданные параметры командной строки.
    //   $ ./args arg1 arg2
    println!("I got {:?} arguments: {:?}.", args.len() - 1, &args[1..]);
}

Запуск:

$ ./main 1 2 3


fn main(){
    let args: Vec = std::env::args().collect(); // возвращает итератор аргументов командной строки
    println!("{:?}", args);// ["target/debug/command-line", "test", "file"] //  первое значение в векторе - "target/debug/command-line" это имя нашего двоичного файла.
}

Запуск:

$ cargo run --bin=command-line test file


use std::env;
fn main() {
    let args: Vec = env::args().collect();
    let query = &args[1];
    let filename = &args[2];
    println!("Searching for {}", query);
    println!("In file {}", filename);
}


use std::env;
fn increase(number: i32) {
    println!("{}", number + 1);
}
fn decrease(number: i32) {
    println!("{}", number - 1);
}
fn help() {
    println!("usage:
match_args 
    Check whether given string is the answer.
match_args {{increase|decrease}} 
    Increase or decrease given integer by one.");
}
fn main() {
    let args: Vec = env::args().collect();
    match args.len() {
        // no arguments passed
        1 => {
            println!("My name is 'match_args'. Try passing some arguments!");
        },
        // one argument passed
        2 => {
            match args[1].parse() {
                Ok(42) => println!("This is the answer!"),
                _ => println!("This is not the answer."),
            }
        },
        // one command and one argument passed
        3 => {
            let cmd = &args[1];
            let num = &args[2];
            // parse the number
            let number: i32 = match num.parse() {
                Ok(n) => {
                    n
                },
                Err(_) => {
                    eprintln!("error: second argument not an integer");
                    help();
                    return;
                },
            };
            // parse the command
            match &cmd[..] {
                "increase" => increase(number),
                "decrease" => decrease(number),
                _ => {
                    eprintln!("error: invalid command");
                    help();
                },
            }
        },
        // all the other cases
        _ => {
            // show a help message
            help();
        }
    }
}

Запуск:

$ ./match_args Rust
This is not the answer.
$ ./match_args 42
This is the answer!
$ ./match_args do something

Достать переменную среды std::env::var

CASE_INSENSITIVE=1 cargo run

fn main(){
    let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
}


fn main(){
    let output = if cfg!(target_os = "windows") {
        Command::new("cmd")
            .arg("/C")
            .arg("echo $".to_owned()+APP_CONF)
            .output()
            .expect("failed to execute process in windows")
    } else {
        std::process::Command::new("sh")
            .arg("-c")
            .arg("echo $".to_owned()+APP_CONF)
            .output()
            .expect("failed to execute process in other OS")
    };

    match std::str::from_utf8(&output.stdout) {
        Ok(result) => {
            match  path() {
                Ok(path) => {
                    if !result.trim().is_empty() {
                        assert_eq!(path.trim(),  result.trim())
                    }else{
                        // else is default
                        assert!(!default_path.is_empty());
                    }
                },
                Err(e) => assert!(false)
            }
        },
        Err(e) => {
            // else is default
            assert!(!default_path.is_empty());
        }
    }

}

Анализатор аргументов

Хотя существует более сложные crate, как clap, мы можем использовать это, чтобы реализовать свой собственный основной аргумент анализатор.

fn parse_args(mut args: &[&str]) -> Args {
    let mut input = String::from("input.txt");
    let mut count = 0;

    loop {
        match args {
            ["-h" | "--help", ..] => {
                eprintln!("Usage: main [--input <filename>] [--count <count>] <args>...");
                std::process::exit(1);
            }
            ["-i" | "--input", filename, rest @ ..] => {
                input = filename.to_string();
                args = rest;
            }
            ["-c" | "--count", c, rest @ ..] => {
                count = c.parse().unwrap();
                args = rest;
            }
            [..] => break,
        }
    }

    let positional_args = args.iter().map(|s| s.to_string()).collect();

    Args {
        input,
        count,
        positional_args,
    }
}

struct Args {
    input: String,
    count: usize,
    positional_args: Vec<String>,
}

Child processes

Структура process::Output представляет выход готового дочернего процесса, а Struct process::Command - это построитель процессов.

struct.Command

rust-by-example/std_misc/process


use std::process::Command;
fn main(){
    let output = Command::new("rustc")
        .arg("--version")
        .output().unwrap_or_else(|e| {
        panic!("failed to execute process: {}", e)
    });
    if output.status.success() {
        let s = String::from_utf8_lossy(&output.stdout);

        print!("rustc succeeded and stdout was:\n{}", s);
    } else {
        let s = String::from_utf8_lossy(&output.stderr);

        print!("rustc failed and stderr was:\n{}", s);
    }
// rustc succeeded and stdout was:
// rustc 1.26.1 (827013a31 2018-05-25)
}

use std::process::Command;
fn main(){
    let mut cmd = Command::new("python");
    cmd.arg("src/bin/decode.py");

    // Execute the command
    match cmd.output() {
        Ok(o) => {
            let res:String = unsafe{
                String::from_utf8_unchecked(o.stdout)

            };
            println!("Output: {}",res);
        },
        Err(e) => {
            eprintln!("There was an error! {}",e);
            process::exit(1);
        }
    }
}

// File: src/bin/decode.py
// print 'Hello I am from Python!'

crate envy

Из переменный среды в конкретный тип, а не строку как в std::env

envy/serde-tests

docs.rs/envy

crate envy

github/envy

Улучшенный std::env

Десериализации env vars в typesafe structs (в типизированные данные)

Типизирует переменные окружения в заданные типы Enum, Struct.

Однако большую часть времени вы хотите работать не только с сырыми строками, но и с типизированными данными.

Он представляет собой бэкенд для serde, который позволяет использовать атрибуты serde для настройки десериализации так, как вы хотите.

SIZE=small cargo run


#[macro_use]
extern crate serde_derive;
extern crate envy;
use std::env;

// SIZE=small cargo run
#[derive(Deserialize, Debug, PartialEq)]
#[serde(untagged)]
#[serde(field_identifier, rename_all = "lowercase")]
pub enum Size {
   Small,
   Medium,
   Large
}

#[derive(Deserialize, Debug)]
struct Config {
 size: Size,
}

fn main() {
   // set env var for size as `SIZE=medium`
   match envy::from_env::() {
      Ok(config) => {println!("{:#?}", config);}
      Err(error) => {panic!("{:#?}", error);}
   }
}

fn main(){
    let data = vec![
        (String::from("ONE"), String::from("test")),
        (String::from("TWO"), String::from("1")),
        (String::from("THREE"), String::from("true")),
    ];
    match envy::prefixed("ENV_VAR_").friter::<_, Config>(data.into_iter()) {
        Ok(config) => {
            println!("{:#?}", config);
        }
        Err(error) => {eprintln!("{:#?}", error);}
    }
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(untagged)]
#[serde(field_identifier, rename_all = "lowercase")]
pub enum Size {
    Small,
    Medium,
    Large,
}
impl Default for Size {
    fn default() -> Size {
        Size::Medium
    }
}
pub fn default_kaboom() -> u16 {
    8080
}
#[derive(Deserialize, Debug, PartialEq)]
pub struct Foo {
    env_var_one: String,
    baz: bool,
    zoom: Option<u16>,
    doom: Vec<u64>,
    #[serde(default = "default_kaboom")]
    kaboom: u16,
    #[serde(default)]
    debug_mode: bool,
    #[serde(default)]
    size: Size,
    provided: Option<String>
}
 
fn main() {
    let data = vec![
        (String::from("ENV_VAR_ONE"), String::from("test")),
        (String::from("BAZ"), String::from("true")),
        (String::from("DOOM"), String::from("1,2,3")),
        (String::from("SIZE"), String::from("small")),
        (String::from("PROVIDED"), String::from("test")),
    ];
    match envy::from_iter::<_, Foo>(data.clone().into_iter()) {
        Ok(config) => {
            println!("{:#?}", config);
        }
        Err(error) => {eprintln!("{:#?}", error);}
    }
    match envy::from_iter::<_, Foo>(data.into_iter()) {
        Ok(foo) => {
            assert_eq!(
                foo,
                Foo {
                    env_var_one: String::from("test"),
                    baz: true,
                    zoom: None,
                    doom: vec![1, 2, 3],
                    kaboom: 8080,
                    debug_mode: false,
                    size: Size::Small,
                    provided: Some(String::from("test")),
                }
            )
        }
        Err(e) => panic!("{:#?}", e),
    }
    let data = vec![
        (String::from("APP_ENV_VAR_ONE"), String::from("test")),
        (String::from("APP_BAZ"), String::from("true")),
        (String::from("APP_DOOM"), String::from("1,2,3")),
        (String::from("APP_SIZE"), String::from("small")),
        (String::from("APP_PROVIDED"), String::from("test")),
    ];
    match envy::prefixed("APP_").from_iter::<_, Foo>(data.clone().into_iter()) {
        Ok(config) => {
            println!("{:#?}", config);
        }
        Err(error) => {eprintln!("{:#?}", error);}
    }
    match envy::prefixed("APP_").from_iter::<_, Foo>(data.into_iter()) {
        Ok(foo) => {
            assert_eq!(
                foo,
                Foo {
                    env_var_one: String::from("test"),
                    baz: true,
                    zoom: None,
                    doom: vec![1, 2, 3],
                    kaboom: 8080,
                    debug_mode: false,
                    size: Size::Small,
                    provided: Some(String::from("test")),
                }
            )
        }
        Err(e) => panic!("{:#?}", e),
    }

Он устанавливает переменные среды из .env файла который начинает искать из папки запуска , если таковые имеются, и сбрасывает данные с фактическими переменными среды, предоставляемыми операционной системой.

dotenv_codegen предоставляет макрос dotenv!, который ведет себя идентично env!, но сначала пытается загрузить .env файл во время компиляции.

Файл Cargo.toml:

[dependencies]
envy = "0.3.2"
serde = "1.0"
serde_derive  = "1.0"
dotenv = "0.13.0"
dotenv_codegen = "0.11.0"

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenv::from_filename(std::path::Path::new("authentication_jwt/.env")).ok();
    let DB = dotenv::var("DB").unwrap();
}

fn main(){
    if let Ok(path) = env::current_dir().and_then(|a| Ok(a.join(".env"))){
        dotenv::from_path(path);
        let key = "ENV_VAR_TWO";
         match env::var_os(key) {
            Some(val) => assert_eq!(std::ffi::OsStr::new("true"),val) ,
            None => assert!(false)
        }
    }else{
           asser!(false);
    }
}

fn main(){
    //.env
    //ENV_VAR_FOUR=true

    // Поиск Файла .env
    // Он загружает файл .env, расположенный в текущем каталоге среды или его родителях последовательно.
    //dotenv().ok();

    //dotenv::from_filename(Path::new("/.env")).ok();

    // Из домашней папки начинает поиск
    //dotenv::from_path(Path::new("./custom.env"));

    // Если есть файл в домашней папке
    //let my_path = env::home_dir().and_then(|a| Some(a.join("/.env"))).unwrap();
    //dotenv::from_path(my_path.as_path());

    let path = env::current_dir().and_then(|a| Ok(a.join(".env"))).unwrap();
    dotenv::from_path(path);

    // dotenv::dotenv_iter()  Список загруженных переменных из файла
    for item in dotenv::dotenv_iter().unwrap() {
        let (key, val) = item.unwrap();
        println!("###{}={}", key, val);
    }
    /*for item in dotenv::dotenv_iter().unwrap() {
        match item {
            Ok(itemval) =>{
                let (key, val) = itemval;
                println!("####{}={}", key, val);
            },
            Err(e) => {continue}
        }
    }*/

    // Извлекает переменную
    let key = "ENV_VAR_FOUR";
    let value = dotenv::var(key).unwrap();
    println!("ENV_VAR_FOUR={}", value);

    // Извлекает все загруженные переменные
    let result: Vec<(String, String)> = dotenv::vars().collect();
}

Пример

Вам не нужно отдельно писать код для парсинга и отдельно — документацию для пользователя. Они объединены в одной строке. Его главная особенность — это декларативный подход, основанный на документации. Обычные библиотеки (like clap в режиме Builder) требуют, чтобы вы императивно описали аргументы, их типы, флаги и т.д., в коде. Docopt работает наоборот.


use docopt::Docopt;
use serde::Deserialize;

// Write the usage string in the docopt format
const USAGE: &str = "
Usage:
    my_program.exe (--input  | --stdin) [--output ] [--verbose]
    my_program.exe --help

Options:
    -i, --input    Input file to process.
    -o, --output   Output file. Defaults to stdout.
    --stdin              Read input from stdin.
    -v, --verbose        Print more debug info.
    -h, --help           Show this help message.
";

// Автоматически генерируем структуру, в которую будут парситься аргументы.
// Docopt использует Serde для этого.
#[derive(Debug, Deserialize)]
struct Args {
    flag_input: Option,
    flag_output: Option,
    flag_stdin: bool,
    flag_verbose: bool,
}
fn main() {
    // Парсим аргументы командной строки согласно нашей спецификации USAGE
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.deserialize())
                            .unwrap_or_else(|e| e.exit());
    println!("{:?}", args);

    // Теперь можно использовать распарсенные значения
    if args.flag_verbose {
        println!("Verbose mode is ON");
    }
    // ... и т.д.
}

Запуск:

my_program --input data.txt -v — будет распаршено корректно.
my_program --input data.txt --output result.txt — тоже сработает.
my_program --help — Docopt автоматически красиво выведет текст из USAGE и завершит программу.
my_program --input data.txt --stdin — вызовет ошибку, потому что в Usage: указано ИЛИ (|).

clap тоже может считывать переменные среды

EnvVariable

[dependencies]
clap = { version = "4.0.0", features = ["derive"] }

use clap::Parser;

#[derive(Parser, Debug)]
#[command(name = "example")]
struct Args {
    /// The input file to process
    #[arg(short, long, env = "INPUT_FILE")]
    input: String,
}
fn main() {
    let args = Args::parse();

    // Печатаем значение аргумента или переменной среды
    println!("Input file: {}", args.input);
}

Запуск:

# Запуск программы с аргументом командной строки
$ cargo run -- --input file.txt

Запуск программы с переменной среды:

$ export INPUT_FILE=file.txt
$ cargo run

//! ## Usage
//! To display the contents of a folder:
//! `` `sh
//! ./executable_file resource-list -d path_dir
//! or short
//! ./executable_file rl -d path_dir
//! ```
//!
//! To display the contents of a file:
//! `` `sh
//! ./executable_file file-content -f path_file.test.js
//! or short
//! ./executable_file fc -f path_file.test.js
//! `` `
//!
//! To execute command `yarn test`
//! `` `sh
//! ./executable_file yarn-test -d path_dir_package_json -f path_file.test.js
//! or short
//! ./executable_file yt -d path_dir_package_json -f path_file.test.js
//! or use full path to `yarn`
//! ./executable_file yarn-test --path-yarn /usr/bin/yarn -d path_dir_package_json -f path_file.test.js
//! `` `

extern crate serde;
extern crate serde_json;
mod errors;
mod functions;
use functions::{wrap_exec_command_yarn_test, wrap_file_content, wrap_resource_list};

use input::{Cli, Commands, Parser};
pub mod input {
    pub use clap::Parser;
    use clap::Subcommand;
    use std::path::PathBuf;
    use once_cell::sync::Lazy;
  
    static DEFAULT_PATH_DIR: Lazy = Lazy::new(|| {
        dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"))
    });

    /// Struct to hold the input arguments
    #[derive(Parser)]
    #[command(version, about, long_about = None)]
    pub struct Cli {
        #[command(subcommand)]
        pub command: Commands,
    }

    #[derive(Subcommand)]
    pub enum Commands {
        /// Show file contents
        #[command(name = "file-content", alias = "fc")]
        FileContent {
            #[arg(short = 'f', long = "path-file", value_name = "PATH_FILE")]
            path_file: PathBuf,
        },
        /// Show folder contents
        #[command(name = "resource-list", alias = "rl")]
        ResourceList {
            #[arg(short = 'd', long = "path-dir", value_name = "PATH_DIR", default_value_os = DEFAULT_PATH_DIR.as_os_str())]
            path_dir: PathBuf,
        },
        /// Command execute
        #[command(name = "yarn-test", alias = "yt")]
        CommandYarnTest {
            #[arg(short = 'p', long = "path-yarn", value_name = "PATH_YARN")]
            path_yarn: Option,
            #[arg(short = 'd', long = "path-dir", value_name = "PATH_DIR_PACKAGE_JSON")]
            path_dir: PathBuf,
            #[arg(short = 'f', long = "path-file", value_name = "PATH_FILE")]
            path_file: PathBuf,
        },
    }
}
fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();
    match &cli.command {
        Commands::FileContent { path_file } => {
            wrap_file_content(path_file);
        }
        Commands::ResourceList { path_dir } => {
            wrap_resource_list(path_dir);
        }
        Commands::CommandYarnTest {
            path_yarn,
            path_dir,
            path_file,
        } => {
            wrap_exec_command_yarn_test(path_yarn.as_ref(), path_dir, path_file);
        }
    }
    Ok(())
}

Управление вводом терминала в неканоническом режиме

Терминалы могут работать в двух режимах:

Канонический режим: в каноническом режиме вводимые пользователем данные обрабатываются построчно, и пользователь должен нажать клавишу Enter, чтобы символы были отправлены в программу для обработки.

Неканонический или необработанный режим: в необработанном режиме ввод с терминала не собирается в строки, но программа может читать каждый символ по мере его ввода пользователем.

Для работы с терминалами Rust предоставляет несколько функций для чтения нажатий клавиш и для управления стандартным вводом и выводом для процесса. Когда пользователь вводит символы в командной строке, сгенерированные байты доступны программе, когда пользователь нажимает клавишу Enter . Это полезно для нескольких типов программ. Но для некоторых типов программ, таких как игры или текстовые редакторы, которые требуют более детального управления, программа должна обрабатывать каждый символ по мере его ввода пользователем, что также известно как необработанный режим. Доступно несколько ящиков сторонних производителей, которые упрощают обработку в необработанном режиме. Мы будем использовать один такой ящик, Термион .

sudo aptitude install smartmontools

Управление терминалом включает в себя возможность выполнять на экране терминала следующие действия:

  • Управление цветом: установка различных цветов переднего плана и фона на терминале и сброс цветов до значений по умолчанию.
  • Управление стилями: установка стиля текста на полужирный, курсив, подчеркивание и т. Д.
  • Управление курсором: установка курсора в определенной позиции, сохранение текущей позиции курсора, отображение и скрытие курсора и другие специальные функции, такие как мигающие курсоры.
  • Обработка событий: прослушивание и реакция на события клавиатуры и мыши.
  • Работа с экраном: переключение с основного экрана на альтернативный и очистка экрана.
  • Необработанный режим: переключение терминала в необработанный режим.