Источник: Nuances of Programming
Играете на гитаре и вынуждены постоянно искать в интернете аккорды для исполнения песен с менее распространенными аккордами?
А как насчет инструмента командной строки (CLI), который принимает на вход название аккорда и выводит аппликатуру, отображающую способ его исполнения?
В статье мы реализуем эту идею и с нуля создадим такой CLI в Rust.
Конечная цель
Итоговый результат работы — создание CLI с именем chord. Он принимает на вход одну строку, а именно название аккорда, и выводит его аппликатуру. Пример:
$ chord C
x ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ ◯ │
├─┼─┼─┼─┼─┤
│ │ ◯ │ │ │
├─┼─┼─┼─┼─┤
│ ◯ │ │ │ │
└─┴─┴─┴─┴─┘
Почему Rust?
Rust выбран не случайно. По результатам опроса Stack Overflow, проведенного в 2021 году, он является самым излюбленным языком программирования:
Rust отлично подходит как для создания бэкенд-сервисов, так и CLI.
К делу!
Установка Rust
Устанавливаем Rust следующим образом:
$ curl --proto '=https' --tlsv1.2 -sSf <https://sh.rustup.rs> | sh
Более подробная информация по установке предоставлена по ссылке.
Создание нового проекта Rust
$ cargo new chord --bin
Created binary (application) `chord` package
Компилируем и запускаем программу, проверяя корректность всех настроек:
$ cd chord
$ cargo run
Hello, world!
Полученный результат:
|-Cargo.toml
|-Cargo.lock
|-src
| |-main.rs
Вывод грифа
У гитары 6 струн. Как правило, для отображения постановки пальцев используется гриф.
На данный момент main.rs выглядит так:
fn main() {
println!("Hello, world!");
}
Внесем в него изменения для вывода пустого грифа:
const FRETBOARD: &str = "◯ ◯ ◯ ◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
└─┴─┴─┴─┴─┘";
fn main() {
println!("{}", FRETBOARD);
}
Выполняем программу и получаем следующий результат:
$ cargo run
◯ ◯ ◯ ◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
└─┴─┴─┴─┴─┘
Clap
Для парсинга аргументов командной строки потребуется библиотека Clap.
Присоединяем к проекту Clap в качестве зависимости:
$ cargo add clap --features derive
Эта команда просто добавляет следующую строку в cargo.toml, где описаны зависимости для Rust:
clap = { version = "3.2.21", features = ["derive"] }
Обновляем main.rs таким образом:
const FRETBOARD: &str = "◯ ◯ ◯ ◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
└─┴─┴─┴─┴─┘";
use clap::Parser;
/// CLI, показывающий, как сыграть гитарный аккорд
#[derive(Parser, Debug)]
#[clap(version, about)]
struct Args {
/// Название аккорда
#[clap()]
name: String,
}
fn main() {
let args = Args::parse();
println!("This is how you play '{}' chord: \n{}", args.name, FRETBOARD);
}
Макросы Rust значительно упрощают для Clap процедуру аннотации Args и обеспечивают бесплатный парсинг аргументов:
/// CLI, показывающий, как сыграть гитарный аккорд
#[derive(Parser, Debug)]
#[clap(version, about)]
struct Args {
/// Название аккорда
#[clap()]
name: String,
}
Проверяем:
$ cargo run -- --help
chord 0.1.0
A CLI to show you how to play a guitar chord
USAGE:
chord <NAME>
ARGS:
<NAME> Name of the chord
OPTIONS:
-h, --help Print help information
-V, --version Print version information
$ cargo run -- C
This is how you play 'C' chord:
◯ ◯ ◯ ◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
└─┴─┴─┴─┴─┘
Двойное тире -- — это общепринятый способ указать на окончание параметров для cargo, а остальное передается программе CLI. Такой принцип действия характерен не только для cargo, но и многих других shell-команд.
Есть еще одна скрытая особенность, на которую стоит обратить внимание. Заметили, что A CLI to show you how to play a guitar chord (CLI, показывающий, как сыграть гитарный аккорд) и Name of the chord (Название аккорда) являются комментариями в исходном коде? Они также включены в справочное сообщение.
Из исходного кода:
/// A CLI to show you how to play a guitar chord
#[derive(Parser, Debug)]
#[clap(version, about)]
struct Args {
/// Name of the chord
#[clap()]
name: String,
}
Из вывода:
chord 0.1.0
A CLI to show you how to play a guitar chord
USAGE:
chord <NAME>
ARGS:
<NAME> Name of the chord
Это вся необходимая информация, которую нужно знать о Clap. Переходим непосредственно к самому CLI.
CLI
Обновляем функцию main следующим образом:
fn main() {
let args = Args::parse();
let chords: HashMap<&str, &str> =
HashMap::from([("C", "x32010"), ("G", "320003"), ("D", "xx0232")]);
match chords.get(&args.name[..]) {
None => println!("Unknown chord '{}'", args.name),
Some(pattern) => {
let mut board: Vec<char> = FRETBOARD.chars().collect();
for (i, ch) in pattern.chars().enumerate() {
let idx: usize = i * 2;
if ch == 'x' {
board[idx] = ch
} else {
let value: usize = ch.to_digit(10).unwrap() as usize;
board[idx] = ' ';
board[idx + 24 * value] = '◯'
}
}
println!(
"This is how you play '{}' chord: \n{}",
args.name,
board.iter().collect::<String>()
)
}
}
}
Если название входного аккорда неизвестно, то выводится сообщение об ошибке Unknown chord. В противном случае мы накладываем постановку пальцев поверх пустого грифа, представленного ранее.
Тестируем код:
$ cargo run -- C
This is how you play 'C' chord:
x ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ ◯ │
├─┼─┼─┼─┼─┤
│ │ ◯ │ │ │
├─┼─┼─┼─┼─┤
│ ◯ │ │ │ │
└─┴─┴─┴─┴─┘
$ cargo run -- Asus4
Unknown chord 'Asus4'
Отличный результат! Быстро и просто!
Установка
До сих пор мы компилировали и запускали CLI. cargo занимается не только сборкой, выполнением, управлением зависимостями библиотек, но и установкой.
Устанавливаем CLI chord:
cargo install --path .
И работаем с данным CLI напрямую:
$ chord G
This is how you play 'G' chord:
◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ ◯ │ │ │ │
├─┼─┼─┼─┼─┤
◯ │ │ │ │ ◯
└─┴─┴─┴─┴─┘
Теперь вы знаете, как создать CLI в Rust.
Заключение
Забыли, как сыграть аккорд? Не беда — у вас под рукой отличный инструмент.
Переходите по ссылке в ветку blog-post-checkpoint, где предоставлен код для данной статьи. С новейшей версией CLI можно ознакомиться по ссылке на ветке master.
Читайте также:
Перевод статьи Yuchen Z.: Build a Command Line Tool With Rust to Play Guitar Chords