🔥 수동 파싱과 테스트

446자
6분

개발자는 종종 프로그램의 커맨드 라인 인자를 직접 파싱하거나 테스트해야 할 때가 있죠. 이번 장에서는 이런 상황을 위해 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의 수동 파싱 기능을 활용하면 이처럼 명령행 인자를 유연하게 처리할 수 있습니다. 필요에 따라 인자를 직접 파싱하고 검증하거나, 파싱 전에 인자를 필터링할 수도 있죠. 상황에 맞는 적절한 방식을 선택해서 사용할 수 있습니다.