🔥 파싱 전략 정하기
ArgumentParser
는 커맨드 라인 입력을 파싱할 때 대시(-
)로 시작하는 키와 대시(-
)로 시작하지 않는 그냥 값을 구분해요. 보통은 키에 해당하는 값을 찾을 때 대시(-
)가 없는 값만 선택합니다.
예를 들어보면, 아래 명령은 --verbose
플래그, --name
옵션, 그리고 file
인자를 정의하고 있어요:
struct Example: ParsableCommand { @Flag var verbose = false @Option var name: String @Argument var file: String? mutating func run() throws { print("Verbose: \(verbose), name: \(name), file: \(file ?? "none")") } }
swift
이 명령을 실행할 때는 --name
옵션 값을 바로 뒤에 써 줘야 해요. 만약 --verbose
플래그가 그 사이에 있으면 파싱이 실패하고 오류가 나버립니다:
% example --verbose --name Tomás Verbose: true, name: Tomás, file: none % example --name --verbose Tomás Error: Missing value for '--name <name>' Usage: example [--verbose] --name <name> [<file>] See 'example --help' for more information.
text
배열 옵션도 비슷해요. 기본적으로는 바로 옆에 있는 키-값 쌍만 인식합니다.
다른 방식의 단일 값 파싱
@Option
을 만들 때 다른 파싱 전략을 주면 이 동작을 바꿀 수 있어요. 다른 파싱 전략을 고를 때는 조심해야 합니다! 사용자가 예상치 못한 동작을 겪을 수 있거든요.
.unconditional
전략은 대시(-
)로 시작하더라도 바로 다음 입력을 값으로 사용합니다. 만약 name
을 @Option(parsing: .unconditional) var name: String
으로 정의했다면, 두 번째 시도에서 "--verbose"
가 name
의 값이 됩니다:
% example --name --verbose Tomás Verbose: false, name: --verbose, file: Tomás
text
반면 .scanningForValue
전략은 커맨드 라인 입력을 앞으로 훑어보면서 대시(-
)가 없는 첫 번째 값을 사용해요. 다른 플래그나 옵션은 그냥 건너뛰기도 합니다. 만약 name
을 @Option(parsing: .scanningForValue) var name: String
으로 정의했다면, 파서는 Tomás
를 찾기 위해 앞으로 살펴봅니다. Tomás
를 name
의 값으로 사용한 후에는 Tomás
바로 다음 인자부터 다시 파싱을 시작하여 --verbose
플래그를 받아들입니다:
% example --name --verbose Tomás Verbose: true, name: Tomás, file: none
text
다른 방식의 배열 파싱
배열 옵션의 기본 파싱 방식은 각 값을 키-값 쌍에서 읽어오는 거예요. 예를 들어, 다음 명령은 입력 파일 이름을 여러 개 받을 수 있습니다:
struct Example: ParsableCommand { @Option var file: [String] = [] @Flag var verbose = false mutating func run() throws { print("Verbose: \(verbose), files: \(file)") } }
swift
단일 값처럼 사용자가 --file
키를 쓸 때마다 값도 함께 제공해야 해요:
% example --verbose --file file1.swift --file file2.swift Verbose: true, files: ["file1.swift", "file2.swift"] % example --file --verbose file1.swift --file file2.swift Error: Missing value for '--file <file>' Usage: example [--file <file> ...] [--verbose] See 'example --help' for more information.
text
.unconditionalSingleValue
전략은 대시(-
)로 시작하더라도 키 다음에 나오는 모든 입력을 값으로 받아들입니다. 만약 file
을 @Option(parsing: .unconditionalSingleValue) var file: [String]
으로 정의했다면, 결과 배열에 옵션처럼 생긴 문자열도 들어갈 수 있어요:
% example --file file1.swift --file --verbose Verbose: false, files: ["file1.swift", "--verbose"]
text
.upToNextOption
전략은 대시(-
)로 시작하는 입력이 나올 때까지 옵션 키 뒤에 오는 모든 값을 사용합니다. 만약 file
을 @Option(parsing: .upToNextOption) var file: [String]
으로 정의했다면, 사용자는 --file
을 반복하지 않고도 여러 파일을 지정할 수 있어요:
% example --file file1.swift file2.swift Verbose: false, files: ["file1.swift", "file2.swift"] % example --file file1.swift file2.swift --verbose Verbose: true, files: ["file1.swift", "file2.swift"]
text
마지막으로 .remaining
전략은 대시(-
) 여부와 상관없이 옵션 키 뒤에 오는 모든 입력을 다 받아들입니다. file
을 @Option(parsing: .remaining) var file: [String]
으로 정의할 경우, --verbose
플래그를 인식하려면 반드시 --file
키 앞에 써 줘야 합니다:
% example --verbose --file file1.swift file2.swift Verbose: true, files: ["file1.swift", "file2.swift"] % example --file file1.swift file2.swift --verbose Verbose: false, files: ["file1.swift", "file2.swift", "--verbose"]
text
다른 방식의 위치 인자 파싱
위치 인자 배열을 파싱하는 기본 방식은 대시(-
)로 시작하는 모든 커맨드 라인 입력을 무시하는 거예요. 예를 들어, 다음 명령은 --verbose
플래그와 위치 인자로 파일 이름을 여러 개 받습니다:
struct Example: ParsableCommand { @Flag var verbose = false @Argument var files: [String] = [] mutating func run() throws { print("Verbose: \(verbose), files: \(files)") } }
swift
files
인자 배열은 기본 .remaining
전략을 사용하므로 대시(-
)가 없는 값만 가져와요:
% example --verbose file1.swift file2.swift Verbose: true, files: ["file1.swift", "file2.swift"] % example --verbose file1.swift file2.swift --other Error: Unexpected argument '--other' Usage: example [--verbose] [<files> ...] See 'example --help' for more information.
text
-
종결자 뒤에 오는 입력은 자동으로 위치 인자로 취급되니까, 사용자는 기본 설정에서도 이렇게 대시()로 시작하는 값을 넘겨줄 수 있습니다:
% example --verbose -- file1.swift file2.swift --other Verbose: true, files: ["file1.swift", "file2.swift", "--other"]
text
.unconditionalRemaining
전략은 알려진 옵션과 플래그를 파싱하고 남은 입력을 전부 가져갑니다. 대시(-
)로 시작하는 입력이나 --
종결자까지도 포함됩니다. 예를 들어, files
를 @Argument(parsing: .unconditionalRemaining) var files: [String]
으로 정의하면, 결과 배열에 옵션 같은 문자열도 포함됩니다:
% example --verbose file1.swift file2.swift --other Verbose: true, files: ["file1.swift", "file2.swift", "--other"] % example -- --verbose file1.swift file2.swift --other Verbose: false, files: ["--", "--verbose", "file1.swift", "file2.swift", "--other"]
text
이렇게 ArgumentParser
에서 여러 가지 파싱 전략을 정하는 방법을 살펴봤어요. 각 전략마다 장단점이 있으니 상황에 맞게 알맞은 걸 고르는 게 중요하겠죠? 보통은 기본 전략으로도 잘 작동하지만, 필요하다면 다른 전략을 써서 사용자에게 좀 더 융통성 있는 커맨드 라인 인터페이스를 제공할 수 있답니다.