🔥 수동 파싱과 테스트
개발자는 종종 프로그램의 커맨드 라인 인자를 직접 파싱하거나 테스트해야 할 때가 있죠. 이번 장에서는 이런 상황을 위해 ArgumentParser가 제공하는 수동 파싱 기능에 대해 알아보겠습니다.
인자 파싱하기
간단한 스위프트 스크립트를 작성할 때는 ParsableArguments
프로토콜을 채택한 타입 하나로 명령행 인자를 파싱할 수 있습니다. 이렇게 하면 코드를 화면 좌측 끝에서부터 곧장 아래로 읽어내려 가면서 작성할 수 있죠.
사용자 정의 검증하기에서 다뤘던 Select
명령을 일반적인 명령 대신 스크립트 스타일로 구현해 보겠습니다. 먼저 ParsableArguments
타입으로 옵션을 정의합니다:
struct SelectOptions: ParsableArguments { @Option var count: Int = 1 @Argument var elements: [String] = [] }
swift
다음으로는 커맨드 라인 입력에서 옵션을 파싱합니다:
let options = SelectOptions.parseOrExit()
swift
parseOrExit()
정적 메서드는 완전히 초기화된 타입의 인스턴스를 반환하거나, 오류 메시지와 코드를 출력하고 프로그램을 종료합니다. 파싱 중 발생하는 오류를 직접 처리하고 싶다면 parse()
메서드를 호출할 수도 있죠.
입력값 검증을 수행하고 필요하다면 스크립트를 종료할 수 있습니다:
guard options.elements.count >= options.count else { let error = ValidationError("'count'는 전체 요소 개수보다 작아야 합니다.") SelectOptions.exit(withError: error) }
swift
exit(withError:)
메서드에 ValidationError
를 전달하면 사용법 정보도 함께 출력합니다.
마지막으로 요청받은 개수만큼의 요소를 출력합니다:
let chosen = options.elements .shuffled() .prefix(options.count) print(chosen.joined(separator: "\n"))
swift
명령 파싱하기
명령을 수동으로 파싱하는 것은 간단한 ParsableArguments
타입을 파싱하는 것보다 좀 더 복잡합니다. 하위 명령 트리에서 파싱한 결과는 트리의 루트와는 다른 타입일 수 있기 때문에 parseAsRoot(_:)
정적 메서드는 타입이 지워진 ParsableCommand
를 반환하죠.
명령과 하위 명령 정의하기에서 정의했던 Math
명령과 하위 명령을 사용해서 이 과정을 살펴보겠습니다. 이번에는 Math.main()
대신 Math.parseAsRoot()
를 호출하고 그 결과를 switch로 처리해 보죠:
do { var command = try Math.parseAsRoot() switch command { case var command as Math.Add: print("당신은 \(command.options.values.count)개의 값을 더하기로 선택했습니다.") command.run() default: print("당신은 다른 작업을 선택했습니다.") try command.run() } } catch { Math.exit(withError: error) }
swift
새로운 로직은 검증과 실행 사이에서 명령을 가로채고 추가 메시지를 출력합니다:
% math 10 15 7 당신은 3개의 값을 더하기로 선택했습니다. 32 % math multiply 10 15 7 당신은 다른 작업을 선택했습니다. 1050
text
명령행 입력 제공하기
parse()
, parseOrExit()
, parseAsRoot()
등 모든 파싱 메서드는 선택적으로 명령행 입력 배열을 인자로 받을 수 있습니다. 이를 활용하면 명령을 테스트하거나, 명령행 인자를 파싱 전에 필터링하거나, 같은 타깃 또는 다른 타깃 내에서 명령을 수동으로 실행할 수 있죠.
위에서 작성한 select
스크립트를 수정해서 파싱 전에 모두 대문자로 이뤄진 단어를 제거해 보겠습니다.
let noShoutingArguments = CommandLine.arguments.dropFirst().filter { phrase in phrase.uppercased() != phrase } let options = SelectOptions.parseOrExit(noShoutingArguments)
swift
이제 명령을 호출할 때 파서는 대문자 단어를 아예 보지 못합니다. HEY
는 절대 출력되지 않겠죠:
% select hi howdy HEY --count 2 hi howdy % select hi howdy HEY --count 2 howdy hi
text
ArgumentParser의 수동 파싱 기능을 활용하면 이처럼 명령행 인자를 유연하게 처리할 수 있습니다. 필요에 따라 인자를 직접 파싱하고 검증하거나, 파싱 전에 인자를 필터링할 수도 있죠. 상황에 맞는 적절한 방식을 선택해서 사용할 수 있습니다.