CLI 앱 다듬기
CLI 앱 다듬기
HelpCommand를 개선해 더 자세한 정보를 제공하고, 유연한 출력 처리를 위한 onOutput 인자를 추가해 봐요.
출처: 원문
본문
이 챕터에서는 command_runner 패키지에 마무리 손질을 가할 거예요. HelpCommand를 다듬어 더 자세한 사용 정보를 제공하고, 더 유연한 출력 처리를 위한 onOutput 인자를 추가할 거예요. 이렇게 하면 CommandRunner 패키지가 완성되어 더 복잡한 시나리오에서 사용할 준비가 돼요.
이번 장에서 할 일
- 문자열을 반복적으로 만드는 방법 배우기
- help 사용 정보 개선하기
- 커맨드 러너를 더 유연하게 만들기
사전 준비
이 챕터를 시작하기 전에 다음을 확인하세요:
- 7장을 마쳤고
dartpedia프로젝트로 작업할 수 있는 Dart 개발 환경이 갖춰져 있을 것. - 변수, 함수, 제어 흐름 같은 기본 프로그래밍 개념에 익숙할 것.
- Dart의 패키지와 라이브러리 개념을 이해하고 있을 것.
- 상속과 추상 클래스 같은 객체지향 프로그래밍 원칙에 익숙할 것.
Tasks
command_runner 패키지를 더 견고하고 사용자 친화적으로 만들도록 다듬을 거예요.
Task 1: HelpCommand 출력 개선하기
옵션과 그 설명을 포함해 더 자세한 사용 정보를 제공하도록 HelpCommand를 개선해요. 이렇게 하면 사용자가 여러분의 CLI 애플리케이션을 어떻게 쓰는지 더 쉽게 이해할 수 있어요.
-
command_runner/lib/src/help_command.dart파일을 여세요. -
파일 맨 위에
console.dart와exceptions.dartimport를 추가하세요. 색 extension을 사용하고ArgumentException을 던지기 위해 이것들이 필요해요.
import 'dart:async';
import 'package:command_runner/command_runner.dart';
import 'console.dart';
import 'exceptions.dart';
- 기존
run메서드를 다음으로 바꾸세요. 이 새 버전은StringBuffer를 사용해 help 문자열을 효율적으로 만들고, 상세(verbose) 출력을 처리하는 로직을 포함해요.
@override
FutureOr<String> run(ArgResults args) async {
final buffer = StringBuffer();
buffer.writeln(runner.usage.titleText);
if (args.flag('verbose')) {
for (var cmd in runner.commands) {
buffer.write(_renderCommandVerbose(cmd));
}
return buffer.toString();
}
if (args.hasOption('command')) {
var (:option, :input) = args.getOption('command');
var cmd = runner.commands.firstWhere(
(command) => command.name == input,
orElse: () {
throw ArgumentException(
'Input $input is not a known command.',
name,
);
},
);
return _renderCommandVerbose(cmd);
}
// Verbose is false and no arg was passed in, so print basic usage.
for (var command in runner.commands) {
buffer.writeln(command.usage);
}
return buffer.toString();
}
StringBuffer는 문자열을 효율적으로 만들 수 있게 해 주는 Dart 클래스예요. 특히 루프 안에서 많은 연결을 수행할 때 + 연산자를 쓰는 것보다 성능이 더 좋아요.
HelpCommand클래스에_renderCommandVerbose비공개 헬퍼 메서드를 추가하세요. 이 메서드는 단일 명령의 상세 출력을 형식화해요.
String _renderCommandVerbose(Command cmd) {
final indent = ' ' * 10;
final buffer = StringBuffer();
buffer.writeln(cmd.usage.instructionText); //abbr, name: description
buffer.writeln('$indent ${cmd.help}');
if (cmd.valueHelp != null) {
buffer.writeln(
'$indent [Argument] Required? ${cmd.requiresArgument}, Type: ${cmd.valueHelp}, Default: ${cmd.defaultValue ?? 'none'}',
);
}
buffer.writeln('$indent Options:');
for (var option in cmd.options) {
buffer.writeln('$indent ${option.usage}');
}
return buffer.toString();
}
Task 2: onOutput 콜백 추가하기
다음으로 CommandRunner에 onOutput 인자를 추가해 유연한 출력 처리를 가능하게 해요.
-
command_runner/lib/src/command_runner_base.dart파일을 여세요. -
CommandRunner생성자에onOutput인자를 추가하고, 클래스에 해당onOutput멤버를 추가하세요.
class CommandRunner {
CommandRunner({this.onOutput, this.onError});
/// If not null, this method is used to handle output. Useful if you want to
/// execute code before the output is printed to the console, or if you
/// want to do something other than print output the console.
/// If null, the onInput method will [print] the output.
FutureOr<void> Function(String)? onOutput;
FutureOr<void> Function(Object)? onError;
// ... rest of the class
}
run메서드를onOutput인자를 사용하도록 갱신하세요.
Future<void> run(List<String> input) async {
try {
final ArgResults results = parse(input);
if (results.command != null) {
Object? output = await results.command!.run(results);
if (onOutput != null) {
await onOutput!(output.toString());
} else {
print(output.toString());
}
}
} on Exception catch (exception) {
if (onError != null) {
onError!(exception);
} else {
rethrow;
}
}
}
이렇게 하면 run 메서드가 onOutput 함수가 제공되면 그것을 사용하고, 그렇지 않으면 콘솔에 출력하는 기본 동작을 해요.
Task 3: onOutput 콜백 사용하기
마지막으로 새 onOutput 기능을 사용하도록 주 애플리케이션을 갱신해요.
-
cli/bin/cli.dart파일을 여세요. -
CommandRunner에onOutput함수를 전달하도록main함수를 갱신하세요.write함수를 사용할 수 있게 하려면console.dartimport도 추가해야 해요.
import 'package:command_runner/command_runner.dart';
const version = '0.0.1';
void main(List<String> arguments) {
var commandRunner = CommandRunner(
onOutput: (String output) async {
await write(output);
},
onError: (Object error) {
if (error is Error) {
throw error;
}
if (error is Exception) {
print(error);
}
},
)..addCommand(HelpCommand());
commandRunner.run(arguments);
}
Task 4: 변경 사항 테스트하기
개선된 HelpCommand와 onOutput 콜백을 테스트해요.
-
터미널을 열고
cli디렉토리로 이동하세요. -
dart run bin/cli.dart help --verbose명령을 실행하세요.
help 명령의 자세한 사용 정보가 사용자 지정 write 함수로 출력되는 걸 볼 수 있을 거예요.
Review
무엇을 해냈나요
이번 레슨에서 만들고 배운 내용을 정리해 볼게요.
-
효율적인 문자열 구축에 StringBuffer 사용하기
루프에서 문자열을 만들 때 더 효율적일 수 있는
StringBuffer로 문자열 연결을 바꿨어요.writeln()과write()같은 메서드가 내용을 추가하고,toString()이 최종 문자열을 만들어 내요. -
상세 출력으로 HelpCommand 개선하기
옵션, 기본값, 설명을 포함한 상세 사용 정보를 보여 주도록 help 시스템을 개선했어요.
--verbose플래그와--command옵션이 사용자에게 세부 수준을 제어할 수 있게 해 줘요. -
유연한 처리를 위한 onOutput 콜백 인자 추가하기
CommandRunner에onOutput을 추가해 라이브러리 사용자가 출력 표시 방식을 사용자 지정할 수 있게 했어요. 이 패턴은 지연 출력, 로깅, 출력 리다이렉션을 가능하게 해 패키지를 더 유연하고 적응력 있게 만들어요.
Quiz
이해를 확인해 봐요
Dart에서 StringBuffer 클래스의 목적은 무엇인가요?
- 여러 부분을 이어 붙여 문자열을 효율적으로 만드는 것 — 맞아요!
StringBuffer는 연속적인 여러 번의 추가 동안 중간 문자열 객체를 만들지 않아 효율적인 문자열 연결을 제공해요. - 고정 크기 문자열을 저장하는 것 — 아니에요. Dart의 문자열은 불변이고 고정 크기라는 개념이 없어요.
StringBuffer는 문자열 생성과 관련된 다른 목적을 가져요. - 문자열을 암호화하는 것 — 아니에요. 암호화는 전용 암호화 라이브러리가 필요해요.
StringBuffer는 보안과 무관한 범용 유틸리티예요. - 두 문자열의 동등성을 비교하는 것 — 아니에요. 문자열 비교는
==나compareTo()로 해요.StringBuffer는 다른 목적을 가져요.
CommandRunner 클래스의 onOutput 인자로 무엇을 할 수 있나요?
- 명령의 출력 처리를 사용자 지정한다 — 맞아요!
onOutput은 형식화, 로깅, 다른 대상에 쓰기 같은 명령 출력의 사용자 지정 동작을 정의하게 해 줘요. - 명령의 입력을 지정한다 — 아니에요. 입력은
run()에 전달되는 커맨드라인 인자에서 와요.onOutput이라는 이름이 다른 무언가를 뜻한다는 걸 암시해요. - 명령의 오류 메시지를 설정한다 — 아니에요. 오류 처리는 별도의 메커니즘(
onError)이 있어요.onOutput은 다른 종류의 결과를 위한 거예요. - 명령의 이름을 정의한다 — 아니에요. 명령 이름은
Command클래스의namegetter에서 정의해요.onOutput은 다른 목적을 가져요.
다음 레슨
다음 레슨에서는 애플리케이션의 Wikipedia 부분을 준비할 거예요.
더 알아보기
- StringBuffer — 문자열을 효율적으로 만드는 방법을 더 알아보세요.
- 콜백 함수 — 함수를 인자로 전달하는 Dart의 함수 기능을 살펴보세요.