durumis AI가 요약한 글
- Rust는 CLI 작성에 매우 적합한 언어로, clap과 ratatui 라이브러리를 이용해 로그인, 로그아웃 기능을 갖는 간단한 CLI 프로그램을 구현했다.
- clap을 통해 다양한 옵션과 서브커맨드를 정의하고, ratatui로 대화형 입력창을 구현하여 편의성과 보안성을 높였다.
- 2024년 4월 18일 기준, 작성자는 Rust로 CLI 프로그램을 편리하게 작성할 수 있는 방법을 소개했다.
내 경험상 CLI를 가장 견고하고 편리하게 작성할 수 있는 언어는 Rust다.
뛰어난 매크로 확장 능력을 통해 구조체 수준에서 모든 제약사항과 체크를 전부 정의할 수 있고, 그 타입의 강력함을 그대로 누릴 수 있기 때문이다.
본 포스트에서는 clap과 ratatui를 이용해서 로그인, 로그아웃을 처리할 수 있는 간단한 CLI 예제 프로그램을 한번 만들어보고, 그것을 통해서 대략적인 작성법과 응용방법을 소개해보겠다.
clap 기본 설정
먼저 종속성에 아래 4가지를 추가해준다.
atty = { version = "0.2.14", optional = true } structopt = { version = "0.3.18", optional = true } clap = { version = "4.4.18", features = ["derive"] } serde = { version = "1", features = ["derive"] }
위에 2개는 cargo 명령 매핑을 위한 것이고, 아래 2개가 clap 파싱을 위한 것이다.
이제 코드를 하나씩 추가해보자.
먼저 최상위 커맨드 타입을 정의하고, 그 내에 서브커맨드 enum을 하나 집어넣는다.
SubCommand enum에는 곧 정의될 로그인과 로그아웃 커맨드가 들어갈 것이다.
그리고 위와 같이 커맨드를 초기화하면 기본적인 틀 작성은 끝났다.
이렇게 만들어진 프로그램은 cargo run을 통해 실행하거나
cargo install을 통해서 설치한 다음에 실행할 수 있다.
서브커맨드 정의하기
이제 하나씩 서브커맨드를 정의해보겠다.
Login과 Logout이 별도의 서브커맨드로 빠질 것이다.
별도의 파일을 만들어서 login 커맨드를 먼저 정의했다.
내부의 Option 객체를 통해서 플래그 요소들을 정의한다.
short은 -i 같은 숏컷을 생성할지를 명시하고, long은 --id 같은 풀텍스트를 생성할지를 명시한다. help는 당연히 도움말이다.
저 플래그들을 Optional 요소로 만들려면 타입을 Option으로 넣거나 default_value 속성을 부여해줘야 한다.
그리고 저걸 아까 최상의 커맨드 객체의 서브커맨드에 variant로 추가한다.
이번엔 logout 커맨드다. 이건 더 쉽다.
옵션을 줄게 딱히 없기 때문이다.
마찬가지로 최상의 커맨드의 서브커맨드로 등록해준다.
진입점에서도 action이 enum으로 분기되니, 패턴매칭으로 적절히 분기해주면 된다.
그리고 실행해보면
서브커맨드 목록에 추가가 된 것을 볼 수 있다.
실행도 잘 된다. 필수 플래그를 빼먹으면 없다고 잔소리한다.
넘겨서 주면 잘 작동할 것이다. 다만 아직은 아무런 동작을 하지 않는다.
action 분리하고 구현하기
이제 구현부를 적절히 추가해보자. 내 경우에는 이러한 상황에서는 파일별로 로직을 분리하는 것을 선호한다. 합치면 너무 보기 힘들고 더러워지기 때문이다.
action 모듈을 두고 각 subcommand 동작마다 파일을 하나씩 뒀다.
각 액션 함수들은 각 서브커맨드의 옵션 값을 인자로 받는다.
그리고 진입점에서의 실행을 run으로 전부 떠넘긴다.
꽤 깔끔해보이지 않나?
이제는 준비가 거의 다 되었으니, 그럴듯한 로직을 구현해보겠다.
로그인은 전달받은 플래그를 기반으로 credentials 파일로 내보내도록 했다.
로그아웃은 생성된 credential 파일을 삭제한다.
그러면 login을 시도하면
입력한대로 생성이 되고
로그아웃을 시도하면
기대한대로 삭제가 될 것이다.
TUI로 대화형 커맨드 구현하기
근데 필수 플래그가 한두개면 모르는데, 플래그가 많아지면 이걸 다 찾아서 입력하는 것도 꽤 고역이다.
또는, 그냥 화면상에 패스워드를 그대로 노출하는게 보안상 문제가 될 수도 있다.
그럴때는 편의성이나 보안을 위해서 TUI를 제공하는 것이 바람직할 수도 있다.
Rust의 꽤 쓸만한 TUI 환경인 ratatui를 사용해서 패스워드 마스킹까지 포함한 대화형 입력창을 구현해보겠다.
먼저 아래 종속성을 2개 추가해준다.
crossterm = "0.27.0" ratatui = "0.26.0"
그리고 대화형 실행을 위한 옵션을 Login 플래그에 추가해줬다.
그 옵션이 들어오면 대화형 컨텍스트로 제어를 이동시킨다.
이제부터 시작이다.
아래는 베이스 코드다.
처음 보면 장황해서 좀 어지러울 수도 있는데, 핵심은 별게 없다.
그냥 무한루프 뺑뺑이돌면서 텍스트를 계속 렌더링할 뿐이다.
그리고 키 입력으로 q가 들어오면 루프를 종료하고 나간다.
저대로 실행해보면
꽤 번쩍거리는 커맨드 컨텍스트로 전환이 된다.
q를 입력하면 종료될 것이다.
이제 입력 과정을 구현해보겠다.
사실 별건 없다.
스텝 과정을 타입으로 정의하고, 현재 스텝 컨텍스트와 입력 변수들을 정의한다.
매번 루프의 시작에서는 현재 스텝에 따라서 안내 텍스트와 현재 입력 상황을 렌더링해준다.
Done으로 종료가 되었을 때는 입력이 다 들어왔을테니, run 함수를 다시 호출해서 기존의 로직을 재사용한다.
이벤트 핸들링은 좀더 장황하다.
각 스텝과 입력에 따라서 입력변수를 수정하거나, 다음 스텝으로 전이할 수 있게 해뒀다.
백스페이스가 들어오면 지우고, 문자가 들어오면 append한다. Esc를 누르면 프로그램이 종료되고, Enter를 누르면 다음 스텝으로 이동한다.
이제 다시 실행해보자.
입력한 대로 잘 들어가고
패스워드도 마스킹된 형태로 잘 들어간다.
다 입력하고 엔터를 치면
프로그램이 종료되고 입력한 값도 잘 방출될 것이다.
이런 느낌으로 쓰면 된다.
노가다가 좀 있긴 하지만 그다지 어렵지는 않다.
전체 코드는 아래 링크에서 볼 수 있다.